{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.9","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":8089,"databundleVersionId":44321,"sourceType":"competition"},{"sourceId":19989,"databundleVersionId":1160143,"sourceType":"competition"}],"dockerImageVersionId":30056,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"\n\n![](https://lmb.informatik.uni-freiburg.de/people/ronneber/u-net/u-net-architecture.png)","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport zipfile\nimport numpy as np\nimport random\nimport os\nfrom tqdm import tqdm\nfrom skimage.io import imread, imshow\nfrom skimage.transform import resize\nimport matplotlib.pyplot as plt\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T15:39:39.397923Z","iopub.execute_input":"2025-08-25T15:39:39.398236Z","iopub.status.idle":"2025-08-25T15:39:39.402808Z","shell.execute_reply.started":"2025-08-25T15:39:39.398198Z","shell.execute_reply":"2025-08-25T15:39:39.401854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SEED = 42\nnp.random.seed = SEED\n\nUNZIP_PATH = '../input/data-science-bowl-2018/'\nTRAIN_PATH = './train/'\nTEST_PATH = './test/'\n\nIMG_WIDTH = 128\nIMG_HEIGHT = 128\nIMG_CHANNELS = 3","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-08-25T15:39:41.921660Z","iopub.execute_input":"2025-08-25T15:39:41.922129Z","iopub.status.idle":"2025-08-25T15:39:41.926507Z","shell.execute_reply.started":"2025-08-25T15:39:41.922099Z","shell.execute_reply":"2025-08-25T15:39:41.925630Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Pomoću biblioteke zipfile radimo extractovanje podataka.Ovaj dataset sadrži slike ćelijskih jezgara. Za svaku sliku imamo onoliko elemenata u masks folderu, koliko ima jezgara na toj slici. ","metadata":{}},{"cell_type":"code","source":"# Unzip data\nwith zipfile.ZipFile(UNZIP_PATH + 'stage1_train.zip', 'r') as zip_ref:\n    zip_ref.extractall('./train')\n    \nwith zipfile.ZipFile(UNZIP_PATH + 'stage1_test.zip', 'r') as zip_ref:\n    zip_ref.extractall('./test')","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:39:43.917749Z","iopub.execute_input":"2025-08-25T15:39:43.918029Z","iopub.status.idle":"2025-08-25T15:39:48.826735Z","shell.execute_reply.started":"2025-08-25T15:39:43.918006Z","shell.execute_reply":"2025-08-25T15:39:48.826064Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Test i Train folderi sadrže podfoldere sa uuidevima, i sadrže podfoldere images i masks, u ovom koraku kupimo ide-ve tih foldera.","metadata":{}},{"cell_type":"code","source":"# get list of all subfolders\ntrain_ids = next(os.walk(TRAIN_PATH))[1]\ntest_ids = next(os.walk(TEST_PATH))[1]\nprint(train_ids[:10])\nprint(test_ids[:10])\n","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:39:48.828556Z","iopub.execute_input":"2025-08-25T15:39:48.828907Z","iopub.status.idle":"2025-08-25T15:39:48.835694Z","shell.execute_reply.started":"2025-08-25T15:39:48.828869Z","shell.execute_reply":"2025-08-25T15:39:48.834874Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Alokacija memorije. X_train je 4d niz sa prvim argumentom kao brojem slika, zatim dimenzijama i kanalima slika. Y_train je niz maski, i njima treba samo jedan kanal jer su binarne. True = Nucleus pixel, False = Pozadinski pixel","metadata":{}},{"cell_type":"code","source":"# define placeholders (also used to replace NaN in images to resize by 0)\nX_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype = np.uint8)\nY_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, 1), dtype = np.bool)","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:39:48.836916Z","iopub.execute_input":"2025-08-25T15:39:48.837262Z","iopub.status.idle":"2025-08-25T15:39:48.847799Z","shell.execute_reply.started":"2025-08-25T15:39:48.837195Z","shell.execute_reply":"2025-08-25T15:39:48.847176Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Ovaj dio predstavlja preprocesiranje. \n* Tqdm je samo progress loader.\n* enumerate(train_ids) -> kreira parove ( index, folder_name ) (0, 'folder1'), (1, 'folder2'), n-> index, id_ -> ime foldera\n* imread(path + '/images/' + id_ + '.png')[:, :, :IMG_CHANNELS]  -> učitava sliku i uzima samo prva 3 kanala u slučaju da slika ima alpha kanal\n* img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True), X_train[n] = img -> resize slike na 128x128, mode='constant' znači da se dodaje padding nulama ako je potrebno, preserve_range=True znači da se zadržavaju originalne vrijednosti pixela. X_train[n] = img upisujemo sliku na n-ti element u datasetu.\n* mask = np.zeros((IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)   -> kreira praznu sliku za masku sa vrijednostima False\n* mask_ = np.expand_dims(resize(mask_, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True), axis=-1) -> dodaje dimenziju da bi maska bila 128, 128, 1\n* np.maximum() -> uzima element-wise maximum. Ovako uzimamo pozitivne piksele prije pozadine.\n\n","metadata":{}},{"cell_type":"code","source":"# Loop through all train_ids\nfor n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):  \n    \n    # Get the path to the image and mask directories\n    path = TRAIN_PATH + id_  \n    \n    # Read the image and keep the first IMG_CHANNELS channels\n    img = imread(path + '/images/' + id_ + '.png')[:, :, :IMG_CHANNELS]  \n    \n    # Resize the image to IMG_HEIGHT x IMG_WIDTH while keeping the pixel values (mode='constant')\n    img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)  \n    \n    # Store the processed image into the X_train array at index n\n    X_train[n] = img \n    \n    # Initialize an empty mask of dimensions IMG_HEIGHT x IMG_WIDTH x 1 with boolean values\n    mask = np.zeros((IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)  \n    \n    # Loop through each file in the masks directory\n    for mask_file in next(os.walk(path + '/masks/'))[2]:  \n        \n        # Read the mask file\n        mask_ = imread(path + '/masks/' + mask_file)\n        \n        # Resize the mask to IMG_HEIGHT x IMG_WIDTH while keeping the pixel values (mode='constant'), and add an extra dimension\n        mask_ = np.expand_dims(resize(mask_, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True), axis=-1)\n        \n        # Merge all the masks associated with the same image by taking the maximum value at each pixel location\n        mask = np.maximum(mask, mask_)\n    \n    # Store the processed mask into the Y_train array at index n\n    Y_train[n] = mask","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:39:50.298245Z","iopub.execute_input":"2025-08-25T15:39:50.298530Z","iopub.status.idle":"2025-08-25T15:43:37.450808Z","shell.execute_reply.started":"2025-08-25T15:39:50.298504Z","shell.execute_reply":"2025-08-25T15:43:37.449858Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Slično kao kod preprocesiranja maski, preprocesiramo test slike. Jedina je razlika što ovdje slike ne skraćujemo na 128x128 nego one zadržavaju originalnu veličinu. To radimo zato što želimo da output bude iste dimenzije kao input za test. Ulaz za UNET mora da bude 128x128 ali cemo ih na kraju u Post Processingu resizovat na originalne dimenzije.  ","metadata":{}},{"cell_type":"code","source":"# Create an empty array for test images with dimensions as (number of test images, height, width, channels)\n# The datatype for the array is uint8 which can hold values from 0 to 255\nX_test = np.zeros((len(test_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype=np.uint8)\n\n# Initialize an empty list to store the original sizes of the test images\nsizes_test = []\n\n# Loop through all the test_ids\nfor n, id_ in tqdm(enumerate(test_ids), total=len(test_ids)):\n    \n    # Get the path to the test image\n    path = TEST_PATH + id_\n    \n    # Read the image and keep the first IMG_CHANNELS channels\n    img = imread(path + '/images/' + id_ + '.png')[:, :, :IMG_CHANNELS]\n    \n    # Append the original size of the image to sizes_test list\n    sizes_test.append([img.shape[0], img.shape[1]])\n    \n    # Resize the image to IMG_HEIGHT x IMG_WIDTH while keeping the pixel values\n    img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)\n    \n    # Store the processed image into the X_test array at index n\n    X_test[n] = img","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:47:40.129234Z","iopub.execute_input":"2025-08-25T15:47:40.129531Z","iopub.status.idle":"2025-08-25T15:47:41.227811Z","shell.execute_reply.started":"2025-08-25T15:47:40.129505Z","shell.execute_reply":"2025-08-25T15:47:41.227106Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n1. Definišemo convolution block, koji sadrži 2 konvolucije, sa aktivacionom funkcijom relu, i \"same\" vrijednost za padding ( dopunjavamo sliku praznim 0 pikselima da bi ostala iste dimenzije). Dropout radimo da se ne bi desio overfitting, a on funkcioniše tako što radi reset na radnom neuronima. Conv2DTranspose radi upsampling. Zatim se radi konkatenacija sa \"skip_connectionsima\" iz enkodera, i onda se kreira konvolucioni blok sa konkateniranom slikom.\n","metadata":{}},{"cell_type":"code","source":"# Create a function for a convolution block\ndef conv_block(inputs, num_filters):\n    x = tf.keras.layers.Conv2D(num_filters, (3, 3), activation=\"relu\", \n                               kernel_initializer=\"he_normal\", padding=\"same\")(inputs)\n    x = tf.keras.layers.Dropout(0.1)(x)\n    x = tf.keras.layers.Conv2D(num_filters, (3, 3), activation=\"relu\", \n                               kernel_initializer=\"he_normal\", padding=\"same\")(x)\n    return x\n\n# Create a function for the expanding path\ndef upsample_block(inputs, conv_prev, num_filters):\n    up = tf.keras.layers.Conv2DTranspose(num_filters, (2, 2), strides=(2, 2), padding=\"same\")(inputs)\n    concat = tf.keras.layers.concatenate([up, conv_prev])\n    conv = conv_block(concat, num_filters)\n    return conv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T15:47:44.636161Z","iopub.execute_input":"2025-08-25T15:47:44.636492Z","iopub.status.idle":"2025-08-25T15:47:44.641604Z","shell.execute_reply.started":"2025-08-25T15:47:44.636430Z","shell.execute_reply":"2025-08-25T15:47:44.640774Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kreira se input objekat, on mora biti ove klase tf.keras.layers.Input. Sa Lambda se radi normalizacija u [0, 1]. Ovo je sami algoritam sa enkoderom i dekoderom. U poslednjem koraku sa 1->1 konvolucijom koristimo sigmoid funkciju za aktivacionu funkciju, jer su naši pikseli rezultata 1 ili 0, tj imamo binarnu klasifikaciju.\n","metadata":{}},{"cell_type":"code","source":"# Inputs\ninputs = tf.keras.layers.Input((IMG_WIDTH, IMG_HEIGHT, IMG_CHANNELS))\n\n# Normalization\ns = tf.keras.layers.Lambda(lambda x: x/255.0)(inputs) \n\n# Contraction path\nc1 = conv_block(s, 16) #(128, 128, 16)\np1 = tf.keras.layers.MaxPooling2D((2, 2))(c1) #(64, 64, 16)\n\nc2 = conv_block(p1, 32) # (64, 64, 32)\np2 = tf.keras.layers.MaxPooling2D((2, 2))(c2) # (32, 32, 32)\n\nc3 = conv_block(p2, 64) # (32, 32, 64)\np3 = tf.keras.layers.MaxPooling2D((2, 2))(c3) # (16, 16, 64)\n\nc4 = conv_block(p3, 128) # (16, 16, 128)\np4 = tf.keras.layers.MaxPooling2D((2, 2))(c4) # (8, 8, 128)\n\nc5 = conv_block(p4, 256) # (8, 8, 256) - Bottleneck\n\n# Expansive path\nc6 = upsample_block(c5, c4, 128) # (16, 16, 128) + skip connection from c4\nc7 = upsample_block(c6, c3, 64)  # (32, 32, 64) + skip connection from c3\nc8 = upsample_block(c7, c2, 32)  # (64, 64, 32) + skip connection from c2\nc9 = upsample_block(c8, c1, 16)  # (128, 128, 16) + skip connection from c1\n\n# Output layer\noutputs = tf.keras.layers.Conv2D(1, (1, 1), activation='sigmoid')(c9)","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:47:47.597120Z","iopub.execute_input":"2025-08-25T15:47:47.597394Z","iopub.status.idle":"2025-08-25T15:47:47.796765Z","shell.execute_reply.started":"2025-08-25T15:47:47.597371Z","shell.execute_reply":"2025-08-25T15:47:47.796107Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model\nmodel = tf.keras.Model(inputs=[inputs], outputs=[outputs])\n\n# Compilation\nmodel.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])\n\n# Summary\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T15:47:50.227362Z","iopub.execute_input":"2025-08-25T15:47:50.227667Z","iopub.status.idle":"2025-08-25T15:47:50.250592Z","shell.execute_reply.started":"2025-08-25T15:47:50.227641Z","shell.execute_reply":"2025-08-25T15:47:50.249835Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Definišemo callbackove koji u toku treniranja modela prate progress, i automatski čuvaju model tokom treniranja.\nModelCheckpoint čuva modele u toku treniranja, da se ne bi izgubio progress ukoliko nešto stane. EarlyStopping zaustavlja model ako on prestane se poboljšava. TensorBoard samo loguje stvari.","metadata":{}},{"cell_type":"code","source":"# Callbacks\ncheckpoint = tf.keras.callbacks.ModelCheckpoint('model.h5', verbose = 1, save_best_only = True)\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(patience = 5, monitor = 'val_loss'),\n    tf.keras.callbacks.TensorBoard(log_dir = 'logs')\n            ]","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:47:56.935035Z","iopub.execute_input":"2025-08-25T15:47:56.935306Z","iopub.status.idle":"2025-08-25T15:47:56.939411Z","shell.execute_reply.started":"2025-08-25T15:47:56.935283Z","shell.execute_reply":"2025-08-25T15:47:56.938555Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = model.fit(\n    X_train, \n    Y_train, \n    validation_split = 0.1, \n    batch_size = 16, \n    epochs = 25, \n    callbacks = callbacks)","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:47:58.861276Z","iopub.execute_input":"2025-08-25T15:47:58.861596Z","iopub.status.idle":"2025-08-25T15:48:24.735509Z","shell.execute_reply.started":"2025-08-25T15:47:58.861564Z","shell.execute_reply":"2025-08-25T15:48:24.734936Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predictions\nidx = random.randint(0, len(X_train))\n\npreds_train = model.predict(X_train[:int(X_train.shape[0]*0.9)], verbose = 1)\npreds_val = model.predict(X_train[int(X_train.shape[0]*0.9):], verbose = 1)\npreds_test = model.predict(X_test, verbose = 1)\n\npreds_train_t = (preds_train > 0.5).astype(np.uint8)\npreds_val_t = (preds_val > 0.5).astype(np.uint8)\npreds_test_t = (preds_test > 0.5).astype(np.uint8)","metadata":{"execution":{"iopub.status.busy":"2025-08-25T15:48:27.378352Z","iopub.execute_input":"2025-08-25T15:48:27.378699Z","iopub.status.idle":"2025-08-25T15:48:28.145794Z","shell.execute_reply.started":"2025-08-25T15:48:27.378668Z","shell.execute_reply":"2025-08-25T15:48:28.145155Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to display images\ndef display_images(image):\n    plt.imshow(image)\n    plt.axis('off')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T15:48:32.565107Z","iopub.execute_input":"2025-08-25T15:48:32.565403Z","iopub.status.idle":"2025-08-25T15:48:32.568666Z","shell.execute_reply.started":"2025-08-25T15:48:32.565377Z","shell.execute_reply":"2025-08-25T15:48:32.567913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Select a random index from training set\nix = random.randint(0, len(preds_train_t))\nprint(len(preds_train_t))\n\nfor i in range(0, 10):\n    ix = random.randint(0, len(preds_val_t))\n    \n    # Note: validation data starts at 90% of training data\n    train_ix = int(X_train.shape[0]*0.9) + ix\n    \n    print(\"Input Image:\")\n    display_images(X_train[train_ix])\n    \n    print(\"Ground Truth Mask:\")\n    display_images(np.squeeze(Y_train[train_ix]))\n    \n    print(\"Predicted Mask:\")\n    display_images(np.squeeze(preds_val_t[ix]))\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T16:05:02.533278Z","iopub.execute_input":"2025-08-25T16:05:02.533707Z","iopub.status.idle":"2025-08-25T16:05:04.480601Z","shell.execute_reply.started":"2025-08-25T16:05:02.533664Z","shell.execute_reply":"2025-08-25T16:05:04.479911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Select a random index from validation set\nix = random.randint(0, len(preds_val_t))\n\n# Calculate the index from where validation data starts\nval_data_start = int(X_train.shape[0] * 0.9)\n\n# Display the validation input image\nprint(\"Validation Input Image:\")\ndisplay_images(X_train[val_data_start:][ix])\n\n# Display the validation ground truth mask\nprint(\"Validation Ground Truth Mask:\")\ndisplay_images(np.squeeze(Y_train[val_data_start:][ix]))\n\n# Display the validation predicted mask\nprint(\"Validation Predicted Mask:\")\ndisplay_images(np.squeeze(preds_val_t[ix]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T15:48:41.609142Z","iopub.execute_input":"2025-08-25T15:48:41.609412Z","iopub.status.idle":"2025-08-25T15:48:41.780647Z","shell.execute_reply.started":"2025-08-25T15:48:41.609387Z","shell.execute_reply":"2025-08-25T15:48:41.779981Z"}},"outputs":[],"execution_count":null}]}