{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":8089,"databundleVersionId":44321,"sourceType":"competition"}],"dockerImageVersionId":283,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n\nfrom subprocess import check_output\nprint(check_output([\"ls\", \"../inputs\"]).decode(\"utf8\"))\n\n# Any results you write to the current directory are saved as output.","metadata":{"_cell_guid":"ba2df6a1-00c4-4116-9678-af554db08837","_uuid":"d53a5b302633391627998ae196d937394eda80c1","execution":{"iopub.status.busy":"2024-05-16T16:46:41.42708Z","iopub.execute_input":"2024-05-16T16:46:41.427529Z","iopub.status.idle":"2024-05-16T16:46:41.483539Z","shell.execute_reply.started":"2024-05-16T16:46:41.427454Z","shell.execute_reply":"2024-05-16T16:46:41.48266Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir ../inputs\n!unzip -n ../input/stage1_train.zip -d ../inputs/stage1_train","metadata":{"execution":{"iopub.status.busy":"2024-05-16T16:46:41.484904Z","iopub.execute_input":"2024-05-16T16:46:41.485204Z","iopub.status.idle":"2024-05-16T16:46:44.08166Z","shell.execute_reply.started":"2024-05-16T16:46:41.485155Z","shell.execute_reply":"2024-05-16T16:46:44.080717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!unzip -n ../input/stage1_test.zip -d ../inputs/stage1_test","metadata":{"execution":{"iopub.status.busy":"2024-05-16T16:46:44.083324Z","iopub.execute_input":"2024-05-16T16:46:44.083632Z","iopub.status.idle":"2024-05-16T16:46:45.10874Z","shell.execute_reply.started":"2024-05-16T16:46:44.083554Z","shell.execute_reply":"2024-05-16T16:46:45.107723Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Importing the other necessary libraries\nimport os\nimport pathlib\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_cell_guid":"ec72492d-2e2d-4182-ae73-71764766b8c6","_uuid":"7a098aababfaf5745cf424f5e909118cb12b5d47","execution":{"iopub.status.busy":"2024-05-16T16:46:45.110497Z","iopub.execute_input":"2024-05-16T16:46:45.110889Z","iopub.status.idle":"2024-05-16T16:46:45.123376Z","shell.execute_reply.started":"2024-05-16T16:46:45.110807Z","shell.execute_reply":"2024-05-16T16:46:45.122608Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Reading the image**","metadata":{"_cell_guid":"14e23e72-e1e6-45e1-b670-876c10aa5320","_uuid":"866b93139279ee05b3e779c3fcb643a3f9435e47"}},{"cell_type":"code","source":"#Importing OpenCV - the computer vision library\nimport cv2","metadata":{"_cell_guid":"f35830c2-58d5-4f44-a68d-688f6a3864bf","_uuid":"9a790c40bfa0a6d1031f5f9440ee00a2ca5c1318","execution":{"iopub.status.busy":"2024-05-16T16:46:45.124522Z","iopub.execute_input":"2024-05-16T16:46:45.124803Z","iopub.status.idle":"2024-05-16T16:46:45.129435Z","shell.execute_reply.started":"2024-05-16T16:46:45.124754Z","shell.execute_reply":"2024-05-16T16:46:45.128784Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Glob the training data and load a single image path\ntraining_paths = pathlib.Path('../inputs/stage1_train').glob('*/images/*.png')\ntraining_sorted = sorted([x for x in training_paths])\nim_path = training_sorted[45]","metadata":{"_cell_guid":"b2a79966-b7cd-49b8-b7fd-1fcd795361e5","_uuid":"7f441ecf3f5dddcf9c9fd6774f51889e9b965a02","execution":{"iopub.status.busy":"2024-05-16T16:46:45.130668Z","iopub.execute_input":"2024-05-16T16:46:45.130905Z","iopub.status.idle":"2024-05-16T16:46:45.174553Z","shell.execute_reply.started":"2024-05-16T16:46:45.130863Z","shell.execute_reply":"2024-05-16T16:46:45.173706Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To read the image \nbgrimg = cv2.imread(str(im_path))\nplt.imshow(bgrimg)\nplt.xticks([]) #To get rid of the x-ticks and y-ticks on the image axis\nplt.yticks([])\nprint('Original Image Shape',bgrimg.shape)","metadata":{"_cell_guid":"faf9532f-eb8d-471c-86a8-5aad87925874","_uuid":"bf81402e9ec3c1ec1169545e32fdcfd06a8c9874","execution":{"iopub.status.busy":"2024-05-16T16:46:45.175883Z","iopub.execute_input":"2024-05-16T16:46:45.176144Z","iopub.status.idle":"2024-05-16T16:46:45.301777Z","shell.execute_reply.started":"2024-05-16T16:46:45.176099Z","shell.execute_reply":"2024-05-16T16:46:45.301077Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To see the structure of the image let's display one row of the image matrix\nprint('The first row of the image matrix contains',len(bgrimg[1]),'pixels')\nprint(bgrimg[1])","metadata":{"_cell_guid":"305e16a1-0e36-4cb0-bdf5-ef6123ae315a","_uuid":"0e18f5a0fb0e741ce736e3b437bdb3c79141b8e6","execution":{"iopub.status.busy":"2024-05-16T16:46:45.303097Z","iopub.execute_input":"2024-05-16T16:46:45.303418Z","iopub.status.idle":"2024-05-16T16:46:45.310434Z","shell.execute_reply.started":"2024-05-16T16:46:45.303363Z","shell.execute_reply":"2024-05-16T16:46:45.309679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The image has been read in the BGR colorspace. We have a third dimension as every pixel is represented by it's B, G and R components. This is the default colorpsace in which images are read in OpenCV. A particular \nBGR/RGB color space is defined by the three chromaticities of the red, green, and blue additive primaries, and can produce any chromaticity that is the triangle defined by those primary colors. In simpler terms - An RGB color can be understood by thinking of it as all possible colors that can be made from three colored lights for red, green, and blue. For more information : https://en.wikipedia.org/wiki/RGB_color_space","metadata":{"_cell_guid":"88b34e10-5c12-4549-975c-8e7a7c461503","_uuid":"0712c09106a4b8b5dfa820d091bdc19619ebfe06"}},{"cell_type":"markdown","source":"## Basic Solution","metadata":{"_cell_guid":"6459c23e-daea-480f-9a01-afcaf511fb90","_uuid":"e56bfeff2a361016620f12fd02fc4994057e6d5a"}},{"cell_type":"markdown","source":"In this section, I've tried to break down Stephen Bailey's fantastic notebook ( https://www.kaggle.com/stkbailey/teaching-notebook-for-total-imaging-newbies) so as to make the concepts simpler to understand","metadata":{"_cell_guid":"ac5ce2da-374a-4d01-a698-2b0122e55c6c","_uuid":"eb279b9360d3ab2aad3248391f0f82b386801419"}},{"cell_type":"code","source":"#To transfrom the colorspace from BGR to grayscale so as to make things simpler\ngrayimg = cv2.cvtColor(bgrimg,cv2.COLOR_BGR2GRAY)","metadata":{"_cell_guid":"5c703e75-c026-4595-9ad8-de31501ff817","_uuid":"dd8194b893e87d106edc3e8c4873447e1d18bbfd","execution":{"iopub.status.busy":"2024-05-16T16:46:45.311679Z","iopub.execute_input":"2024-05-16T16:46:45.311946Z","iopub.status.idle":"2024-05-16T16:46:45.319863Z","shell.execute_reply.started":"2024-05-16T16:46:45.311881Z","shell.execute_reply":"2024-05-16T16:46:45.319037Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To plot the image\nplt.imshow(grayimg,cmap='gray') #cmap has been used as matplotlib uses some default colormap to plot grayscale images\nplt.xticks([]) #To get rid of the x-ticks and y-ticks on the image axis\nplt.yticks([])\nprint('New Image Shape',grayimg.shape)","metadata":{"_cell_guid":"2931d307-8d6b-496f-87e5-f275c8af4247","_uuid":"66057751d2f802290fd4cd541d9d1600362bd3cd","execution":{"iopub.status.busy":"2024-05-16T16:46:45.321137Z","iopub.execute_input":"2024-05-16T16:46:45.321415Z","iopub.status.idle":"2024-05-16T16:46:45.436968Z","shell.execute_reply.started":"2024-05-16T16:46:45.321355Z","shell.execute_reply":"2024-05-16T16:46:45.43612Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It is important to understand the structure of the image here. We reduced a dimension when we transformed from the BGR colorspace to grayscale. Why did this happen? This is because grayscale is a range of monochromatic shades from black to white. Therefore, a grayscale image contains only shades of gray and no color (i.e it primarily contains only black and white). Transforming the colorspace removes all color information, leaving only the luminance of each pixel. Since digital images are displayed using a combination of red, green, and blue (RGB) colors, each pixel has three separate luminance values. Therefore, these three values must be combined into a single value when removing color from an image. Luminance can also be described as brightness or intensity, which can be measured on a scale from black (zero intensity) to white (full intensity)","metadata":{"_cell_guid":"8781f2ca-4599-4be5-97ba-113b5aa0d3fe","_uuid":"8a7f578d464093474ade21b3dd58f02954640c65"}},{"cell_type":"code","source":"#To understand this further, let's display one entire row of the image matrix\nprint('The first row of the image matrix contains',len(grayimg[1]),'pixels')\nprint(grayimg[1])","metadata":{"_cell_guid":"265ec972-afbf-48b6-92de-2170725bc35e","_uuid":"10db9741dff4415f0985c0c4d0546c59487379c9","execution":{"iopub.status.busy":"2024-05-16T16:46:45.438352Z","iopub.execute_input":"2024-05-16T16:46:45.438687Z","iopub.status.idle":"2024-05-16T16:46:45.447542Z","shell.execute_reply.started":"2024-05-16T16:46:45.438628Z","shell.execute_reply":"2024-05-16T16:46:45.446717Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Thus this displays one entire row of the image matrix with the corresponding luminance or intensities of every pixel","metadata":{"_cell_guid":"75156eeb-4ed6-412a-be55-b47fa413abed","_uuid":"8a8a9550e649df3c78468c9378dc47f1eba3980d"}},{"cell_type":"markdown","source":"**Removing the background**","metadata":{"_cell_guid":"48c826de-f6f3-499c-8794-9268cfdc3e65","_uuid":"d8ee013744f82953ac1a11bfc90ff406c47c0ce8"}},{"cell_type":"code","source":"#Okay let's look at the distribution of the intensity values of all the pixels\nplt.figure(figsize=(10,5))\n\nplt.subplot(1,2,1)\nsns.distplot(grayimg.flatten(),kde=False)#This is to flatten the matrix and put the intensity values of all the pixels in one single row vector\nplt.title('Distribution of intensity values')\n\n#To zoom in on the distribution and see if there is more than one prominent peak \nplt.subplot(1,2,2)\nsns.distplot(grayimg.flatten(),kde=False) \nplt.ylim(0,30000) \nplt.title('Distribution of intensity values (Zoomed In)')","metadata":{"_cell_guid":"8d487379-ea6d-42d2-ae03-d52c135188fc","_uuid":"b75cd7a4a1c9bc10ae7482fb1bd0e12047f32cc2","execution":{"iopub.status.busy":"2024-05-16T16:46:45.448781Z","iopub.execute_input":"2024-05-16T16:46:45.449173Z","iopub.status.idle":"2024-05-16T16:46:45.885664Z","shell.execute_reply.started":"2024-05-16T16:46:45.449117Z","shell.execute_reply":"2024-05-16T16:46:45.884852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see that there are 2 prominent peaks. The count of pixels with intensity values around 0 is extrememly high (250000). We would expect this to occur as the nuclei cover a smaller portion of the picture as compared to the background which is primarily black. Our job here is to seperate the two, that is, seperate the nuclei from the background. The optimal seperation value is somewhere around 20 but rather than relying on such descriptive statistics, we should take a more formal approach such as using Otsu's method.\nOtsu's method, named after Nobuyuki Otsu is used to automatically perform clustering-based image thresholding, or, the reduction of a graylevel image to a binary image. The algorithm assumes that the image contains two classes of pixels following bi-modal histogram (foreground pixels and background pixels), it then calculates the optimum threshold separating the two classes so that their combined spread (intra-class variance) is minimal, or equivalently, so that their inter-class variance is maximal. Otsu’s method exhibits relatively good performance if the histogram can be assumed to have bimodal distribution and assumed to possess a deep and sharp valley between two peaks (source : https://en.wikipedia.org/wiki/Otsu%27s_method) ","metadata":{"_cell_guid":"b8ebb992-1d69-4b23-8210-3de46ba99ac4","_uuid":"6f04d335a4111284e0e83aeada76e54cb12989f5"}},{"cell_type":"code","source":"from skimage.filters import threshold_otsu\nthresh_val = threshold_otsu(grayimg)\nprint('The optimal seperation value is',thresh_val)","metadata":{"_cell_guid":"f0818f3d-09e3-42f4-a957-e71113eac096","_uuid":"8de96e891bccdf041636785699450acdbfe62589","execution":{"iopub.status.busy":"2024-05-16T16:46:45.886818Z","iopub.execute_input":"2024-05-16T16:46:45.887108Z","iopub.status.idle":"2024-05-16T16:46:45.897507Z","shell.execute_reply.started":"2024-05-16T16:46:45.887055Z","shell.execute_reply":"2024-05-16T16:46:45.896666Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we'll use the np.where function to encode all pixels with an intensity value > the threshold value as 1 and all other pixels as 0.  The result of this function will be stored in a variable called mask","metadata":{"_cell_guid":"3e69d253-0073-48a4-b9f2-b2a1afe9ee9c","_uuid":"8ba94cc2e0254cd275603f0b1d8902567e9fe11d"}},{"cell_type":"code","source":"mask=np.where(grayimg>thresh_val,1,0)","metadata":{"_cell_guid":"9184b1f2-6813-482b-8610-80950e725591","_uuid":"4bf0da02a6151ab1665172a7237792ebc32daf48","execution":{"iopub.status.busy":"2024-05-16T16:46:45.898728Z","iopub.execute_input":"2024-05-16T16:46:45.898994Z","iopub.status.idle":"2024-05-16T16:46:45.903862Z","shell.execute_reply.started":"2024-05-16T16:46:45.898949Z","shell.execute_reply":"2024-05-16T16:46:45.903144Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To plot the original image and mask side by side\nplt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nplt.imshow(grayimg,cmap='gray')\nplt.title('Original Image')\n\nplt.subplot(1,2,2)\nmaskimg = mask.copy()\nplt.imshow(maskimg, cmap='viridis')\nplt.title('Mask')\n","metadata":{"_cell_guid":"efb88dac-e47f-407f-b148-69d799cc6c24","_uuid":"b76c8ad7fd1bad64d5b467a7902149cd193c5067","execution":{"iopub.status.busy":"2024-05-16T16:46:45.905185Z","iopub.execute_input":"2024-05-16T16:46:45.905437Z","iopub.status.idle":"2024-05-16T16:46:46.194154Z","shell.execute_reply.started":"2024-05-16T16:46:45.905392Z","shell.execute_reply":"2024-05-16T16:46:46.193324Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We see that the mask has done a decent job. If these images were to appear in a newspaper column titled 'Spot the difference between' (except the obvious colour difference), it would have had people scratch their heads in frustration. However a more careful look suggests that the mask hasn't found out all the nuclei, especially the two in the top right corner. Around the (500,400) mark, the three nuclei have been all combined together to form one cluster. The darker coloured nuclei are causing a problem as the pixels that represent these nuclei have intensity values lesser than Otsu's threshold value.","metadata":{"_cell_guid":"6f4c74fc-97a2-4c0a-b0ee-64ad72a5c96a","_uuid":"8265549971d112680dd79672046a4c19ffd530e2"}},{"cell_type":"code","source":"#Let's see if K-Means does a good job on this data \nfrom sklearn.cluster import KMeans\nkmeans=KMeans(n_clusters=2) #2 as we're still trying to seperate the lighter coloured nuclei from the darker coloured background \nkmeans.fit(grayimg.reshape(grayimg.shape[0]*grayimg.shape[1],1))\n\nplt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nplt.imshow(kmeans.labels_.reshape(520,696),cmap='magma')\nplt.title('K-Means')\n\nplt.subplot(1,2,2)\nplt.imshow(maskimg, cmap='viridis')\nplt.title('Mask with Otsu Seperation')","metadata":{"_cell_guid":"fcd0e6d4-526c-4faf-ba8e-7965c9482905","_uuid":"75629c529dde9b68bc99679428a8909222baf7fc","execution":{"iopub.status.busy":"2024-05-16T16:46:46.195335Z","iopub.execute_input":"2024-05-16T16:46:46.195597Z","iopub.status.idle":"2024-05-16T16:46:47.25258Z","shell.execute_reply.started":"2024-05-16T16:46:46.195538Z","shell.execute_reply":"2024-05-16T16:46:47.251875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It's extrememly hard to tell if there's a difference. Let's see if there is any difference by comparing the labels of Otsu and K-Means at a pixel level, summing over the booleans and dividing them by the total number of pixels in the image. If the result is 1, it means there is no difference at all","metadata":{"_cell_guid":"2af4d70e-b5c9-4279-8c58-6a835e6d3fd9","_uuid":"a9ecf81bfb3933e60b722da553f92c1a071c4437"}},{"cell_type":"code","source":"#To check if there's any difference\nsum((kmeans.labels_.reshape(520,696)==mask).flatten())/(mask.shape[0]*mask.shape[1])","metadata":{"_cell_guid":"6f7bbd23-9252-4df8-b083-11a008ba1d27","_uuid":"fd8b06ba6f9d338268d10890b1e25c7d16b4772f","execution":{"iopub.status.busy":"2024-05-16T16:46:47.253718Z","iopub.execute_input":"2024-05-16T16:46:47.253972Z","iopub.status.idle":"2024-05-16T16:46:48.16167Z","shell.execute_reply.started":"2024-05-16T16:46:47.253927Z","shell.execute_reply":"2024-05-16T16:46:48.160723Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There is no difference at all. For a deeper explanantion as to why this could have happened, one may read D Liu's paper (http://ieeexplore.ieee.org/document/5254345/?reload=true) where he has compared K-Means with Otsu's method ","metadata":{"_cell_guid":"d341e110-ed5b-4d9e-9406-e94a7e554063","_uuid":"c736c738012429044f8c874b323508c9f33b49ed"}},{"cell_type":"markdown","source":"**Object identification**","metadata":{"_cell_guid":"9d7a7185-3199-4d39-94e4-d3bef71d30f4","_uuid":"46318c8debbdae699a5e18b2faf40c2289e64b4b"}},{"cell_type":"markdown","source":"To get a count of the total number of nuclei, we can use the ndimage.label function which labels features (pixels) in an array based on their interconnectedness. So for example if [1 1 1 0 0 1 1] was our row vector, using ndimage.label on this would give us [1 1 1 0 0 2 2] signifying the fact that there are 2 distinct objects in the row vector. The function returns the labeled array and the number of distinct objects it found in the array.\n\nFor more information on connected components : http://aishack.in/tutorials/pixel-neighbourhoods-connectedness/ ","metadata":{"_cell_guid":"2e1c6030-f2f3-4ba1-a294-69edfd380c02","_uuid":"3b18bf8faca0a2cc2ee5d168db7fd270c30119d1"}},{"cell_type":"code","source":"from scipy import ndimage","metadata":{"_cell_guid":"32aef83c-f180-471e-8419-8d8c79f06f5e","_uuid":"1833a01a4d9499e55166135affcf05800ebf4202","execution":{"iopub.status.busy":"2024-05-16T16:46:48.162952Z","iopub.execute_input":"2024-05-16T16:46:48.163217Z","iopub.status.idle":"2024-05-16T16:46:48.167202Z","shell.execute_reply.started":"2024-05-16T16:46:48.163173Z","shell.execute_reply":"2024-05-16T16:46:48.166435Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To see this at a matrix level\nmatrix = np.array([[0,0,1,1,1,1],\n                  [0,0,0,0,1,1],\n                  [1,1,0,1,1,1],\n                  [1,1,0,1,1,1]])\nmatrix","metadata":{"_cell_guid":"928b55d1-03f8-407f-8c09-af9799d12793","_uuid":"a1c2482e5599e745e69830c476f98ca8aab923a4","execution":{"iopub.status.busy":"2024-05-16T16:46:48.16813Z","iopub.execute_input":"2024-05-16T16:46:48.168385Z","iopub.status.idle":"2024-05-16T16:46:48.179365Z","shell.execute_reply.started":"2024-05-16T16:46:48.168341Z","shell.execute_reply":"2024-05-16T16:46:48.17853Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Applying the ndimage.label function\nndimage.label(matrix)","metadata":{"_cell_guid":"a4881854-b30a-4423-8626-f77197b05de2","_uuid":"f8ef9a4a5ff4805f675980e76bdcac81003150e4","execution":{"iopub.status.busy":"2024-05-16T16:46:48.180662Z","iopub.execute_input":"2024-05-16T16:46:48.180998Z","iopub.status.idle":"2024-05-16T16:46:48.191527Z","shell.execute_reply.started":"2024-05-16T16:46:48.180933Z","shell.execute_reply":"2024-05-16T16:46:48.190753Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels,nlabels=ndimage.label(mask)\nprint('There are',nlabels,'distinct nuclei in the mask.')","metadata":{"_cell_guid":"6f1bd8ad-111c-4979-b11f-eacf5c5405f3","_uuid":"4598f6919fdfdffbe6dfaa6076298327da956156","execution":{"iopub.status.busy":"2024-05-16T16:46:48.192826Z","iopub.execute_input":"2024-05-16T16:46:48.193184Z","iopub.status.idle":"2024-05-16T16:46:48.203956Z","shell.execute_reply.started":"2024-05-16T16:46:48.193126Z","shell.execute_reply":"2024-05-16T16:46:48.2031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now, there could be more nuclei than that as some nuclei have been combined into one and our mask hasn't been able to identify all the nuclei, especially the ones in the top right corner. However the 2 seperate spots in the top right corner get labelled as 2 different objects.\n\nAll in all the two major problems in this image are:\n- Insignificant spots/dots being labelled as nuclei. These spots should have their labels (KMeans, Otsu) set to 0 if their sizes are too small. This problem has been caused by some nuclei that have pixels where the intensity values are lesser than Otsu's threshold value, thus causing only some pixels to have their label encoded as 1.\n- The nuclei that are closer to one another get clustered to form one nuclei. So we need to seperate them using some edge detection algorithm (like convolution with a sobel filter or canny edge detector as suggested by Ramsu)\n\nNow for this competition we need to have a seperate mask for every nucleus. In the file named 'stage1_train_labels.csv.zip', we have the image IDs in one column and the Run Length Encoded (RLE) vector for one such mask (i.e for one nucleus) in the other column.","metadata":{"_cell_guid":"c99ccc4c-dd14-4d3f-8cd4-fd074b2c5f8d","_uuid":"bba8540ae572b47313acfa27dccd8268dd5eaab1"}},{"cell_type":"code","source":"#Since we need to create a seperate mask for every nucelus, let's store the masks in an iterable like a list \nlabel_array=[]\n#We need to iterate from 1 as ndimage.label encodes every object starting from number 1\nfor i in range(1,nlabels+1):\n    label_mask = np.where(labels==i,1,0)\n    label_array.append(label_mask)\n#To see one such mask\nlabel_array[68]","metadata":{"_cell_guid":"9504ab83-eca1-4c45-9e71-2c9ae9da9b3d","_uuid":"67e7111134f58789ab574cf4dfe22102fb865edd","execution":{"iopub.status.busy":"2024-05-16T16:46:48.205379Z","iopub.execute_input":"2024-05-16T16:46:48.205732Z","iopub.status.idle":"2024-05-16T16:46:48.323794Z","shell.execute_reply.started":"2024-05-16T16:46:48.205664Z","shell.execute_reply":"2024-05-16T16:46:48.323004Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The 1s represent 1 such object (nucleus) in the entire picture.","metadata":{"_cell_guid":"4f217479-ec77-4173-9e24-45223a05cb9f","_uuid":"9ce13bd0e86e50985cc087d3d14a1c36bc97f356"}},{"cell_type":"markdown","source":"**Run Length Encoding**","metadata":{"_cell_guid":"18cc602b-d815-4667-b9dc-aa8e142adef1","_uuid":"2fb9d567c5857f654105f17bcc70746fc6434aeb"}},{"cell_type":"markdown","source":" Every mask for every nucleus requires an RLE vector. This is the format required by the competition. \n\nWhat is RLE?\n\nRLE or Run Length Encoding converts a matrix into a vector and returns the position/starting point of the first pixel from where we observe an object (identified by a 1) and gives us a count of how many pixels from that pixel we see the series of 1s. In the ndimage.label function example of [1 1 1 0 0 1 1], running RLE would give us 1 3 6 2, which means 3 pixels from the zeroth pixel (inclusive) and 2 pixels from the 5th pixel we see a series of 1s","metadata":{"_cell_guid":"2a136957-b22a-4486-a9db-be863edbb201","_uuid":"be9c57f91bc79ba366de0d3bc56795df2877c88a"}},{"cell_type":"code","source":"#Function for rle encoding\ndef rle(x):\n    '''\n    x: numpy array of shape (height, width), 1 - mask, 0 - background\n    Returns run length as list\n    '''\n    dots = np.where(x.T.flatten()==1)[0] # .T sets Fortran order down-then-right\n    run_lengths = []\n    prev = -2\n    for b in dots:\n        if (b>prev+1): run_lengths.extend((b+1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return \" \".join([str(i) for i in run_lengths])","metadata":{"_cell_guid":"e31bff7f-f58d-4795-9e46-ca2ee4b02b07","_uuid":"365620aa033f4637ef1b80177c73a9c06996f2da","execution":{"iopub.status.busy":"2024-05-16T16:46:48.325134Z","iopub.execute_input":"2024-05-16T16:46:48.32547Z","iopub.status.idle":"2024-05-16T16:46:48.336892Z","shell.execute_reply.started":"2024-05-16T16:46:48.325405Z","shell.execute_reply":"2024-05-16T16:46:48.336056Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Credit to Kaggle user rahlkin https://www.kaggle.com/rakhlin/fast-run-length-encoding-python for developing this function that has been used by many Kagglers for the purpose of this competition.","metadata":{"_cell_guid":"4c53cc55-d5fe-4089-ad36-49d7e0096634","_uuid":"3f95f3ec367c7326316609b4642ceb076fcb878c"}},{"cell_type":"code","source":"#Running RLE on the last label_mask in label_array gives us \nrle(label_mask)","metadata":{"_cell_guid":"9876cdda-4f07-4e16-9a73-74118a95bd53","_uuid":"5f60eb923ec64626269ec40f373a6b3c52d6d7b4","execution":{"iopub.status.busy":"2024-05-16T16:46:48.337989Z","iopub.execute_input":"2024-05-16T16:46:48.338312Z","iopub.status.idle":"2024-05-16T16:46:48.350792Z","shell.execute_reply.started":"2024-05-16T16:46:48.338251Z","shell.execute_reply":"2024-05-16T16:46:48.349995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Putting everything together**","metadata":{"_cell_guid":"e85d164c-527b-4609-a805-ccdc78cf2005","_uuid":"d5e7fdbb6ec7ff98fc1ad750166c9300711efc4b"}},{"cell_type":"code","source":"#To take a look at the different parts\nim_path.parts","metadata":{"_cell_guid":"aa89c6ad-ed7e-4eb0-823d-3bdc07c74cee","_uuid":"124a7d3608f3f0a1b76f6a77bb22bf2ad3241e49","execution":{"iopub.status.busy":"2024-05-16T16:46:48.351957Z","iopub.execute_input":"2024-05-16T16:46:48.352189Z","iopub.status.idle":"2024-05-16T16:46:48.358412Z","shell.execute_reply.started":"2024-05-16T16:46:48.352148Z","shell.execute_reply":"2024-05-16T16:46:48.357677Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Now defining a function that is applicable to all images\ndef basic(im_path):\n    #Reading the image\n    im_id=im_path.parts[-3] #To extract the image ID\n    bgr = cv2.imread(str(im_path)) #Reading it in OpenCV\n    gray = cv2.cvtColor(bgr,cv2.COLOR_BGR2GRAY) #Converting everything to grayscale from BGR\n\n    #To remove the background\n    thresh_val = threshold_otsu(gray) #Using Otsu's method to seperate the foreground objects from the background\n    mask = np.where(gray > thresh_val, 1, 0) #Coding objects with intensity values higher than background as 1\n    \n    #Extracting connected objects\n    test_rle=pd.DataFrame()\n    labels, nlabels = ndimage.label(mask) #labels gives us the label of the different objects in every image starting from 1 and nlabels gives us the total number of objects in every image\n    for i in range(1,nlabels+1): #Iterating through every object/label\n        label_mask = np.where(labels==i,1,0) #Individual masks for every nucleus\n        RLE = rle(label_mask) #RLE for every mask\n        solution = pd.Series({'ImageId': im_id, 'EncodedPixels': RLE})\n        test_rle = test_rle.append(solution, ignore_index=True)\n    \n    #Return the dataframe\n    return(test_rle)\n        ","metadata":{"_cell_guid":"4d40d223-e9b1-4969-a0c3-854a6dad73a8","_uuid":"a3a9028f8c0c6cf1d136875187c5518bfb4522c2","execution":{"iopub.status.busy":"2024-05-16T16:46:48.359457Z","iopub.execute_input":"2024-05-16T16:46:48.359743Z","iopub.status.idle":"2024-05-16T16:46:48.387544Z","shell.execute_reply.started":"2024-05-16T16:46:48.359694Z","shell.execute_reply":"2024-05-16T16:46:48.386584Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Defining a function that takes a list of image paths (pathlib.Path objects), analyzes each and returns a submission ready DataFrame\ndef list_of_images(im_path_list):\n    all_df = pd.DataFrame()\n    for im_path in im_path_list: #We'll use this for the test images\n        im_df = basic(im_path) #Creating one dataframe for every image \n        all_df = all_df.append(im_df, ignore_index=True) #Appending all these dataframes\n    \n    #Returing the submission ready dataframe\n    return (all_df)","metadata":{"_cell_guid":"0debb19e-6615-4a4f-ab80-2d8f136ad660","_uuid":"5b1e32e62b4f24561f155613bba3cd388b523110","execution":{"iopub.status.busy":"2024-05-16T16:46:48.388761Z","iopub.execute_input":"2024-05-16T16:46:48.389045Z","iopub.status.idle":"2024-05-16T16:46:48.400479Z","shell.execute_reply.started":"2024-05-16T16:46:48.388985Z","shell.execute_reply":"2024-05-16T16:46:48.399707Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Final submission\ntest_images = pathlib.Path('../inputs/stage1_test/').glob('*/images/*.png')\nbasic_solution = list_of_images(list(test_images))\nbasic_solution.to_csv('basic_solution.csv', index=None)","metadata":{"_cell_guid":"e2f620b5-72f0-4707-b79e-1ab9c9395200","_uuid":"187367714dfde0697dfe69da22cf0d5303fbd849","execution":{"iopub.status.busy":"2024-05-16T16:46:48.4017Z","iopub.execute_input":"2024-05-16T16:46:48.402162Z","iopub.status.idle":"2024-05-16T16:47:02.823616Z","shell.execute_reply.started":"2024-05-16T16:46:48.40211Z","shell.execute_reply":"2024-05-16T16:47:02.82261Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The submission scored 0.201 which gives us our baseline accuracy. Any layer of complexity that we add onto this should better this score, failing which it is absolutely useless.\n\nSome important questions to ask are:\n\n- Will we achieve a satisfactory performance by converting all pictures to grayscale? What are the different types of pictures in the dataset?\n- What are the numerous ways to seperate the background from objects of interest? Otsu's method requires computing a graylevel histogram for us to find the optimum seperation value. In that respect KMeans may work better on images that aren't particularly grayscale or on images where there is no sharp contrast in the intensity values between objects of interest and the background\n- What are some useful edge detection algorithms to create boundaries between nuclei that are extremely close to one another?\n- How do we as humans identify objects in an image? We indeed take it for granted but if we think of objects as anything that has a fixed shape and size and is prominent with respect to the background, what is the technical (or computer) definition of these terms?","metadata":{"_cell_guid":"65eceb60-5e1f-4554-83f6-249d33b8d9a8","_uuid":"03ebb84cd739c922354abbc3881ef8a739b12951"}},{"cell_type":"markdown","source":"## Edge Detection ","metadata":{"_cell_guid":"a168cd57-5eaa-4871-b0dd-c0d6291d5862","_uuid":"172e47125671400400b7fa04a3b34a92da3d0955"}},{"cell_type":"markdown","source":"Some important videos to watch before beginning this section - https://www.youtube.com/watch?v=XuD4C8vJzEQ&index=2&list=PLkDaE6sCZn6Gl29AoE31iwdVwSG-KnDzF and https://www.youtube.com/watch?v=am36dePheDc&list=PLkDaE6sCZn6Gl29AoE31iwdVwSG-KnDzF&index=3F to get an idea of the underlying matrix algebra in edge detection. Credit to Andrew Ng for always explaining the intuition behind a particular method before going into the more complex math\n\nThe first thing we'll be trying out is the Sobel Filter. A clear and concise explanation of the filter and its usage in Python (OpenCV) is given here : https://docs.opencv.org/3.2.0/d2/d2c/tutorial_sobel_derivatives.html\n","metadata":{"_cell_guid":"ad1b9bee-20ca-4fcb-b530-2abd0f27c57e","_uuid":"dbacbf9e57c0784c58fc0abe92df567ef13b72dc"}},{"cell_type":"code","source":"#cv2.Sobel arguments - the image, output depth, order of derivative of x, order of derivative of y, kernel/filter matrix size\nsobelx = cv2.Sobel(grayimg,int(cv2.CV_64F),1,0,ksize=3) #ksize=3 means we'll be using the 3x3 Sobel filter\nsobely = cv2.Sobel(grayimg,int(cv2.CV_64F),0,1,ksize=3)\n\n#To plot the vertical and horizontal edge detectors side by side\nplt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nplt.imshow(sobelx,cmap='gray')\nplt.title('Sobel X (vertical edges)')\nplt.xticks([])\nplt.yticks([])\n\nplt.subplot(1,2,2)\nplt.imshow(sobely,cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('Sobel Y (horizontal edges)')","metadata":{"_cell_guid":"abc08622-1252-4541-9025-47743668c0c7","_uuid":"44ba65ff4f45b3a970378c2246e8c409208605cc","execution":{"iopub.status.busy":"2024-05-16T16:47:02.824903Z","iopub.execute_input":"2024-05-16T16:47:02.825209Z","iopub.status.idle":"2024-05-16T16:47:03.103072Z","shell.execute_reply.started":"2024-05-16T16:47:02.82515Z","shell.execute_reply":"2024-05-16T16:47:03.102361Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Plotting the original image\nplt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nplt.imshow(grayimg,cmap='gray')\nplt.title('Original image')\n\n#Now to combine the 2 sobel filters\nsobel = np.sqrt(np.square(sobelx) + np.square(sobely))\nplt.subplot(1,2,2)\nplt.imshow(sobel,cmap='gray')\nplt.title('Sobel Filter')","metadata":{"_cell_guid":"bf478446-d637-40cc-ac60-20a168b126d4","_uuid":"bbd0edd7a03632f00a43a77c27cf401992d913eb","execution":{"iopub.status.busy":"2024-05-16T16:47:03.104174Z","iopub.execute_input":"2024-05-16T16:47:03.104437Z","iopub.status.idle":"2024-05-16T16:47:03.449629Z","shell.execute_reply.started":"2024-05-16T16:47:03.104385Z","shell.execute_reply":"2024-05-16T16:47:03.448722Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Aha! The Sobel filter has done better than Otsu/KMeans in identifying distinct objects in the image. The two nuclei in the top right corner, the two extremely small nuclei at the (530,410) mark (tentative) have been identified. However minor concerns remain as 2 of the 3 overlapping nuclei in the same region have been considered as 1, instead of all 3 before.","metadata":{"_cell_guid":"784eceaa-848a-449f-a788-d7b28aca7200","_uuid":"1f4c34ab63c0111b21c9d174ba9d70b1532c49f5"}},{"cell_type":"code","source":"#To highlight the problem areas\nplt.figure(figsize=(12,6))\nplt.subplot(1,3,1)\nplt.imshow(grayimg[350:450,485:530],cmap='gray')\nplt.title('Original image (zoomed in)')\nplt.xticks([])\nplt.yticks([])\n\nplt.subplot(1,3,2)\nplt.imshow(sobel[350:450,485:530],cmap='gray')\nplt.title('Sobel Filter (zoomed in)')\nplt.xticks([])\nplt.yticks([])\n\nplt.subplot(1,3,3)\nplt.imshow(maskimg[350:450,485:530], cmap='gray')\nplt.title('Otsu/K-Means (zoomed in)')\nplt.xticks([])\nplt.yticks([])","metadata":{"_cell_guid":"20b8df76-8ab5-4041-a523-348d44d916db","_uuid":"d96b19acbee2d12698df2bc9a1c79ad643136a22","execution":{"iopub.status.busy":"2024-05-16T16:47:03.451113Z","iopub.execute_input":"2024-05-16T16:47:03.451454Z","iopub.status.idle":"2024-05-16T16:47:03.691413Z","shell.execute_reply.started":"2024-05-16T16:47:03.451393Z","shell.execute_reply":"2024-05-16T16:47:03.690671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So there is definitely an improvement. However as pointed out here (https://www.kaggle.com/c/data-science-bowl-2018/discussion/47864), this problem is an instance segmentation problem. Distinguishing foreground objects from background is not the primary objective. If we fail to include a mask for a particular nucleus (like in the above mentioned example of the 2 overlapping nuclei), our score goes down","metadata":{"_cell_guid":"ae017ddc-ca70-4635-a996-85c2d3b5bdc5","_uuid":"d7716d818a183db106b9e0d45ac6c34fd2d63d83"}},{"cell_type":"markdown","source":"Now, let's try the Canny edge detector which is a smarter Sobel Filter. The Canny edge detector is a multistage algorithm:-\n- The first stage removes the background noise in the image using a Gaussian filter so that the algorithm detects real edges\n- The second stage finds an intensity gradient in the image using a Sobel filter (using a combination of sobel-x and sobel-y)\n- In the third stage unwanted pixels are removed so they will not be confused as edge. To do this, the entire image is analyzed, checking if each pixel is a local maximum in the direction of the gradient relative to its area. Finally the last stage is the application of the Hysteresis Thresholding\n- In this final stage the algorithm determines which edges are real edge and and those who are not at all. For this you must determine two threshold values, the minVal the minimum threshold, and maxVal the maximum threshold. Any edge with an intensity gradient greater than maxval is sure to be an edge, and those with a value less than minVal will be discarded, because they are nor real edge. For all other edge that may be found in the range between these two threshold values, are subjected to a further analysis, establishing whether they are real edges through their connectivity\n\nRead more at http://www.meccanismocomplesso.org/en/opencv-python-canny-edge-detection/#RqetSzirOmJRYDup.99","metadata":{"_cell_guid":"10abacbc-8252-4c14-91a6-f600cefd1e0a","_uuid":"9af18647ebb5bd05ccc9e50c494c7057875a877a"}},{"cell_type":"code","source":"plt.figure(figsize=(12,6))\n\nplt.subplot(1,2,1)\nplt.imshow(grayimg,cmap='gray')\nplt.title('Original image')\nplt.xticks([])\nplt.yticks([])\n\n#Let's see how the Canny Edge Detector does on the image\nplt.subplot(1,2,2)\ncanny = cv2.Canny(grayimg,0,21)\nplt.imshow(canny,cmap='gray')\nplt.title('Canny Edge Detection')\nplt.xticks([])\nplt.yticks([])","metadata":{"_cell_guid":"56d6e53b-08b1-4de1-8607-7d86e87674b7","_uuid":"5cf5998f69d55d59e742da9b936e10d5d217c148","execution":{"iopub.status.busy":"2024-05-16T16:47:03.692775Z","iopub.execute_input":"2024-05-16T16:47:03.6931Z","iopub.status.idle":"2024-05-16T16:47:03.913235Z","shell.execute_reply.started":"2024-05-16T16:47:03.693037Z","shell.execute_reply":"2024-05-16T16:47:03.912463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So the Canny Edge Detector has found gradient within the nuclei as well which gives an impression that it is an overkill. However if we were to retrieve the external contours only and create masks based on these external contours, then we may create masks that capture the region of interest. One point to note is that the same problems that we faced with Sobel filter are visible here, however the Canny Edge Detector has returned a modified image matrix where we only have binary values (0 and 255)","metadata":{"_cell_guid":"26adbfc9-22e6-4adc-b471-52ea3eeea7ad","_uuid":"2cdd5c36c7ef52eba0b94d5f4b6d9eca96938766"}},{"cell_type":"code","source":"#Using contouring to create the masks\ncanny_cont=cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1] #Using an approximation function to obtain the contour points and retreiving only the external contours\n\n#To show the contour points\nplt.figure(figsize=(14,8))\nplt.imshow(canny,cmap='gray')\nplt.title('Canny Edge Detection with contours')\nplt.xticks([])\nplt.yticks([])\n\nfor i in (range(len(canny_cont))):\n    plt.scatter(canny_cont[i].flatten().reshape(len(canny_cont[i]),2)[:,0],\n         canny_cont[i].flatten().reshape(len(canny_cont[i]),2)[:,1])","metadata":{"_cell_guid":"b6d0d146-a194-4b9a-88eb-82c82d4682b0","_uuid":"ac723fd8827722675e614838d35ad96024d85faf","scrolled":true,"execution":{"iopub.status.busy":"2024-05-16T16:47:03.914352Z","iopub.execute_input":"2024-05-16T16:47:03.914623Z","iopub.status.idle":"2024-05-16T16:47:04.66566Z","shell.execute_reply.started":"2024-05-16T16:47:03.91455Z","shell.execute_reply":"2024-05-16T16:47:04.664926Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nplt.imshow(grayimg, cmap='gray')\nplt.title('Original Image')\n\n#Now to create masks with contours\nbackground=np.zeros(grayimg.shape)\ncanny_mask=cv2.drawContours(background,canny_cont,-1,255,-1)\n\nplt.subplot(1,2,2)\nplt.imshow(canny_mask,cmap='gray')\nplt.title('Creating masks with contours')\nplt.xticks([])\nplt.yticks([])","metadata":{"_cell_guid":"c62afc54-a850-4d21-90ce-1e4f486db2ba","_uuid":"bbf7884d66e9b88eefa8502320dc1566792f52ab","execution":{"iopub.status.busy":"2024-05-16T16:47:04.666772Z","iopub.execute_input":"2024-05-16T16:47:04.667044Z","iopub.status.idle":"2024-05-16T16:47:05.077446Z","shell.execute_reply.started":"2024-05-16T16:47:04.667Z","shell.execute_reply":"2024-05-16T16:47:05.076703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now, the Canny Edge Detector has been able to find out most of the nuclei, however we aren't getting complete masks for each of the nuclei. This could be changed by using different values for the minval and maxval parameters (in the cv2.Canny() function) which also depends on the kind of image that we are dealing with (as we will see later). The output of the canny_mask matrix is in the form that allows us to use ndimage.labels, the function used for finding connected components. However it is absolutely important that we make complete masks for each nuclei so that we don't find more objects than there are in this image. ","metadata":{"_cell_guid":"eec9ba57-fde0-4add-a205-9f8612246374","_uuid":"7e6a6a8cd9d200f5cbb9a8167d740f940f5aebd8"}},{"cell_type":"code","source":"canny_mask_copy=canny_mask.copy()\ncanny_mask_clabels=ndimage.label(canny_mask_copy)[0]\nfor label_ind, label_mat in enumerate(ndimage.find_objects(canny_mask_clabels)):\n    cell = canny_mask_clabels[label_mat]\n    #Toheck if the label size is too small\n    if np.product(cell.shape) < 100:\n        canny_mask_clabels[np.where(canny_mask_clabels==label_ind+1)]=1\ncanny_mask_clabels=np.where(canny_mask_clabels>1,0,canny_mask_clabels)\n\n#To show the original mask\nplt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nplt.imshow(canny_mask,cmap='gray')\nplt.title('Masks created with edge plus contour detection')\nplt.xticks([])\nplt.yticks([])\n\n#To plot the problem areas\nplt.subplot(1,2,2)\nplt.imshow(canny_mask_clabels,cmap='gray')\nplt.title('Incomplete Masks')\nplt.xticks([])\nplt.yticks([])","metadata":{"_cell_guid":"1804fdcb-af17-49b0-9f50-148fd2f76bef","_uuid":"43fac331232ae52ad81e977e43799e3bd3c9111a","execution":{"iopub.status.busy":"2024-05-16T16:47:05.078633Z","iopub.execute_input":"2024-05-16T16:47:05.078873Z","iopub.status.idle":"2024-05-16T16:47:05.957145Z","shell.execute_reply.started":"2024-05-16T16:47:05.078831Z","shell.execute_reply":"2024-05-16T16:47:05.95638Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#For convolving 2D arrays\nfrom scipy import signal","metadata":{"_cell_guid":"d3e07fa1-8cd3-4e2a-94ce-55371e4fa8c2","_uuid":"8b6745ac609ad3a41bdabffd320418419ace8d57","execution":{"iopub.status.busy":"2024-05-16T16:47:05.958623Z","iopub.execute_input":"2024-05-16T16:47:05.958968Z","iopub.status.idle":"2024-05-16T16:47:05.963496Z","shell.execute_reply.started":"2024-05-16T16:47:05.958909Z","shell.execute_reply":"2024-05-16T16:47:05.962588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(12,6))\nplt.subplot(1,2,1)\nsns.distplot(np.where(canny_mask==255,1,0).flatten())\nplt.title('Canny Mask')\n\nplt.subplot(1,2,2)\n#To smooth the canny_mask by convolving with a matrix that has all values = 1/9\ncanny_mask_smooth=signal.convolve2d(np.where(canny_mask==255,1,0),np.full((3,3),1/9),'same')\nsns.distplot(canny_mask_smooth.flatten())\ncanny_mask_smooth_thresh=threshold_otsu(canny_mask_smooth)\nplt.axvline(x=canny_mask_smooth_thresh)\nplt.title('Smoothened Canny Mask with Otsu threshold value')","metadata":{"_cell_guid":"60a4097c-e483-497f-9a61-eefe898563a1","_uuid":"b11220523b1398be7edc8ccd45c7f0798f004168","execution":{"iopub.status.busy":"2024-05-16T16:47:05.964749Z","iopub.execute_input":"2024-05-16T16:47:05.965091Z","iopub.status.idle":"2024-05-16T16:47:06.498228Z","shell.execute_reply.started":"2024-05-16T16:47:05.96503Z","shell.execute_reply":"2024-05-16T16:47:06.497382Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The number of pixels with intensity values = 1 has reduced. Why has this happened? This is because of smoothing. We have convolved the canny mask with a local filter (a 3x3 matrix with all values = 1/9) and what this does is that it replaces the intensity values of the pixels by the average of the intensity values of the neighboring pixels. Now if a pixel has all neighbouring pixels with intensity values = 1, the intensity value of the pixel stays as 1 (as 1/9 x 9 =1). However the pixels at the edges of the objects and at the problem areas have reduced intensity values","metadata":{"_cell_guid":"45c2db18-16d7-4f10-aa83-bdecff69140e","_uuid":"a11e092368769ef4d8e43d5f2ee2cd6fa5b21b3c"}},{"cell_type":"code","source":"plt.figure(figsize=(12,6))\nplt.imshow(canny_mask_smooth,cmap='gray')\nplt.title('Smoothened canny mask')\nplt.xticks([])\nplt.yticks([])","metadata":{"_cell_guid":"0e602650-e79e-4ae4-9123-16ed6c51951e","_uuid":"66c437e89c7810aa271e2bea64a4f8f41a47ad92","execution":{"iopub.status.busy":"2024-05-16T16:47:06.499611Z","iopub.execute_input":"2024-05-16T16:47:06.499974Z","iopub.status.idle":"2024-05-16T16:47:06.632605Z","shell.execute_reply.started":"2024-05-16T16:47:06.499911Z","shell.execute_reply":"2024-05-16T16:47:06.631812Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Setting all values above otsu's threshold as 0 in the matrix and in this image matrix setting all values above 0 as 1 \nplt.figure(figsize=(12,6))\ncanny_conv1=np.where(np.where(canny_mask_smooth>canny_mask_smooth_thresh,0,canny_mask_smooth)>0,1,0)\nplt.imshow(canny_conv1,cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('After 1 convolution')","metadata":{"_cell_guid":"44ab26ed-b28a-46bc-b616-c3aaf8e37981","_uuid":"a7bad35c9820a498f2e04e5d36c67f8bf18b84f1","execution":{"iopub.status.busy":"2024-05-16T16:47:06.633996Z","iopub.execute_input":"2024-05-16T16:47:06.634328Z","iopub.status.idle":"2024-05-16T16:47:06.764085Z","shell.execute_reply.started":"2024-05-16T16:47:06.634272Z","shell.execute_reply":"2024-05-16T16:47:06.763252Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(12,6))\ncanny_mask_smooth2=signal.convolve2d(canny_conv1,np.full((3,3),1/9),'same')\ncanny_mask_smooth_thresh2=threshold_otsu(canny_mask_smooth2)\ncanny_conv2=np.where(canny_mask_smooth2>canny_mask_smooth_thresh2,1,0)\nplt.imshow(canny_conv2,cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('After 2 convolutions')","metadata":{"_cell_guid":"d5c9b802-5d3a-4ede-9c85-98789b5cab8d","_uuid":"359736b04167295cc8871af9ead411ec5fc343f5","execution":{"iopub.status.busy":"2024-05-16T16:47:06.765287Z","iopub.execute_input":"2024-05-16T16:47:06.765533Z","iopub.status.idle":"2024-05-16T16:47:06.915805Z","shell.execute_reply.started":"2024-05-16T16:47:06.765484Z","shell.execute_reply":"2024-05-16T16:47:06.915039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Combing the 2 convolutions \ncanny_cont=cv2.findContours(cv2.convertScaleAbs(canny_conv2),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\nbackground=np.zeros(grayimg.shape)\ncanny_mask=cv2.drawContours(background,canny_cont,-1,255,-1)\n\nplt.figure(figsize=(12,6))\nplt.imshow(canny_mask,cmap='gray')\nplt.title('Contour detection after 2 convolutions')\nplt.xticks([])\nplt.yticks([])","metadata":{"_cell_guid":"92c44c26-2cb4-4014-a921-df07df7d02e3","_uuid":"4d9ba444755161200b073292842626ea52f6ac1a","execution":{"iopub.status.busy":"2024-05-16T16:47:06.916914Z","iopub.execute_input":"2024-05-16T16:47:06.917186Z","iopub.status.idle":"2024-05-16T16:47:07.043623Z","shell.execute_reply.started":"2024-05-16T16:47:06.91713Z","shell.execute_reply":"2024-05-16T16:47:07.042933Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"So this looks good. Some of the earlier problems remain as some nuclei are still clustered together but the bottomline is that we have been able to identify all the nuclei in the original picture. Before we try any of this and end up overfitting to a well behaved image, it is important to see what values for MinVal and MaxVal parameters in the cv2.Canny() function work on other images.","metadata":{"_cell_guid":"c653998d-4d8d-4567-a786-2a88c96abb40","_uuid":"500a8a86cc5e2f56568001bdf7608c00156b17b8"}},{"cell_type":"code","source":"#Let's try the same parameters for canny edge on other types of images - starting with another black background and white foreground image\nfor i in range(len(training_sorted)):\n    if training_sorted[i].parts[-1]=='feffce59a1a3eb0a6a05992bb7423c39c7d52865846da36d89e2a72c379e5398.png':\n        bwimg=cv2.imread(str(training_sorted[i]))\n        bwimg=cv2.cvtColor(bwimg,cv2.COLOR_BGR2RGB)\n        plt.figure(figsize=(20,8))\n        plt.subplot(1,3,1)\n        plt.imshow(bwimg)\n        plt.title('Black background and white foreground')\n        \n        plt.subplot(1,3,2)\n        bwimg=cv2.cvtColor(bwimg,cv2.COLOR_RGB2GRAY)\n        bwimg_canny=cv2.Canny(bwimg,0,21)\n        plt.imshow(bwimg_canny,cmap='gray')\n        plt.title('Canny edge detection')\n        \n        plt.subplot(1,3,3)\n        bwimg_cont=cv2.findContours(bwimg_canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        #Now to create masks with contours\n        bwimg_bg=np.zeros(bwimg.shape)\n        bwimg_mask=cv2.drawContours(bwimg_bg,bwimg_cont,-1,255,-1)\n        \n        #Convolving once\n        bwimg_mask_smooth=signal.convolve2d(np.where(bwimg_mask==255,1,0),np.full((3,3),1/9),'same')\n        bwimg_mask_smooth_thresh=threshold_otsu(bwimg_mask_smooth)\n        bwimg_conv1=np.where(np.where(bwimg_mask_smooth>bwimg_mask_smooth_thresh,0,bwimg_mask_smooth)>0,1,0)\n        \n        #Convolving again\n        bwimg_mask_smooth2=signal.convolve2d(bwimg_conv1,np.full((3,3),1/9),'same')\n        bwimg_mask_smooth_thresh2=threshold_otsu(bwimg_mask_smooth2)\n        bwimg_conv2=np.where(bwimg_mask_smooth2>bwimg_mask_smooth_thresh2,1,0)\n        \n        #Now to create masks with contours after 2 convolutions\n        bwimg_cont=cv2.findContours(cv2.convertScaleAbs(bwimg_conv2),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        bwimg_bg=np.zeros(bwimg.shape)\n        bwimg_mask=cv2.drawContours(bwimg_bg,bwimg_cont,-1,255,-1)\n\n        plt.imshow(bwimg_mask,cmap='gray')\n        plt.title('Contour detection after 2 convolutions')","metadata":{"_cell_guid":"654c1ba6-28cf-4c6c-9af7-6919e782fd94","_uuid":"25c29733a7b254ec3f984d142bc956c24db4b8c9","execution":{"iopub.status.busy":"2024-05-16T16:47:07.044715Z","iopub.execute_input":"2024-05-16T16:47:07.044957Z","iopub.status.idle":"2024-05-16T16:47:07.557911Z","shell.execute_reply.started":"2024-05-16T16:47:07.044915Z","shell.execute_reply":"2024-05-16T16:47:07.557094Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Purple background and purple foreground\nfor i in range(len(training_sorted)):\n    if training_sorted[i].parts[-1]=='0e21d7b3eea8cdbbed60d51d72f4f8c1974c5d76a8a3893a7d5835c85284132e.png':\n        ppimg=cv2.imread(str(training_sorted[i]))\n        ppimg=cv2.cvtColor(ppimg,cv2.COLOR_BGR2RGB)\n        plt.figure(figsize=(20,8))\n        plt.subplot(1,3,1)\n        plt.imshow(ppimg)\n        plt.title('Purple background and purple foreground')\n        \n        plt.subplot(1,3,2)\n        ppimg=cv2.cvtColor(ppimg,cv2.COLOR_RGB2GRAY)\n        ppimg_canny=cv2.Canny(ppimg,20,100)\n        plt.imshow(ppimg_canny,cmap='gray')\n        plt.title('Canny edge detection')\n        \n        plt.subplot(1,3,3)\n        ppimg_cont=cv2.findContours(ppimg_canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        #Now to create masks with contours\n        ppimg_bg=np.zeros(ppimg.shape)\n        ppimg_mask=cv2.drawContours(ppimg_bg,ppimg_cont,-1,255,-1)\n        \n        #Convolving once\n        ppimg_mask_smooth=signal.convolve2d(np.where(ppimg_mask==255,1,0),np.full((3,3),1/9),'same')\n        ppimg_mask_smooth_thresh=threshold_otsu(ppimg_mask_smooth)\n        ppimg_conv1=np.where(np.where(ppimg_mask_smooth>ppimg_mask_smooth_thresh,0,ppimg_mask_smooth)>0,1,0)\n        \n        #Convolving again\n        ppimg_mask_smooth2=signal.convolve2d(ppimg_conv1,np.full((3,3),1/9),'same')\n        ppimg_mask_smooth_thresh2=threshold_otsu(ppimg_mask_smooth2)\n        ppimg_conv2=np.where(ppimg_mask_smooth2>ppimg_mask_smooth_thresh2,1,0)\n        \n        #Now to create masks with contours after 2 convolutions\n        ppimg_cont=cv2.findContours(cv2.convertScaleAbs(ppimg_conv2),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        ppimg_bg=np.zeros(ppimg.shape)\n        ppimg_mask=cv2.drawContours(ppimg_bg,ppimg_cont,-1,255,-1)\n\n        plt.imshow(ppimg_mask,cmap='gray')\n        plt.title('Contour detection after 2 convolutions')","metadata":{"_cell_guid":"c37f254e-0438-468e-8cab-0af2029f04a7","_uuid":"ed68fa6b7871cd1d55d0d525952405dee951d4c1","execution":{"iopub.status.busy":"2024-05-16T16:47:07.559271Z","iopub.execute_input":"2024-05-16T16:47:07.559605Z","iopub.status.idle":"2024-05-16T16:47:08.181764Z","shell.execute_reply.started":"2024-05-16T16:47:07.559527Z","shell.execute_reply":"2024-05-16T16:47:08.180963Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#White background and purple foreground\nfor i in range(len(training_sorted)):\n    if training_sorted[i].parts[-1]=='0121d6759c5adb290c8e828fc882f37dfaf3663ec885c663859948c154a443ed.png':\n        wpimg=cv2.imread(str(training_sorted[i]))\n        wpimg=cv2.cvtColor(wpimg,cv2.COLOR_BGR2RGB)\n        plt.figure(figsize=(20,8))\n        plt.subplot(1,3,1)\n        plt.imshow(wpimg)\n        plt.title('White background and purple foreground')\n        \n        plt.subplot(1,3,2)\n        wpimg=cv2.cvtColor(wpimg,cv2.COLOR_RGB2GRAY)\n        wpimg_canny=cv2.Canny(wpimg,20,100)\n        plt.imshow(wpimg_canny,cmap='gray')\n        plt.title('Canny edge detection')\n        \n        plt.subplot(1,3,3)\n        wpimg_cont=cv2.findContours(wpimg_canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        #Now to create masks with contours\n        wpimg_bg=np.zeros(wpimg.shape)\n        wpimg_mask=cv2.drawContours(wpimg_bg,wpimg_cont,-1,255,-1)\n        \n        #Convolving once\n        wpimg_mask_smooth=signal.convolve2d(np.where(wpimg_mask==255,1,0),np.full((3,3),1/9),'same')\n        wpimg_mask_smooth_thresh=threshold_otsu(wpimg_mask_smooth)\n        wpimg_conv1=np.where(np.where(wpimg_mask_smooth>wpimg_mask_smooth_thresh,0,wpimg_mask_smooth)>0,1,0)\n        \n        #Convolving again\n        wpimg_mask_smooth2=signal.convolve2d(wpimg_conv1,np.full((3,3),1/9),'same')\n        wpimg_mask_smooth_thresh2=threshold_otsu(wpimg_mask_smooth2)\n        wpimg_conv2=np.where(wpimg_mask_smooth2>wpimg_mask_smooth_thresh2,1,0)\n        \n        #Now to create masks with contours after 2 convolutions\n        wpimg_cont=cv2.findContours(cv2.convertScaleAbs(wpimg_conv2),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        wpimg_bg=np.zeros(wpimg.shape)\n        wpimg_mask=cv2.drawContours(wpimg_bg,wpimg_cont,-1,255,-1)\n\n        plt.imshow(wpimg_mask,cmap='gray')\n        plt.title('Contour detection after 2 convolutions')","metadata":{"_cell_guid":"27e3da48-c825-43cf-8b78-a0c1b0f1053c","_uuid":"c82def13a3c7598ddb19d4ceb53b3eec2242397a","execution":{"iopub.status.busy":"2024-05-16T16:47:08.183117Z","iopub.execute_input":"2024-05-16T16:47:08.183383Z","iopub.status.idle":"2024-05-16T16:47:08.779588Z","shell.execute_reply.started":"2024-05-16T16:47:08.183335Z","shell.execute_reply":"2024-05-16T16:47:08.77881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#White background and black foreground\nfor i in range(len(training_sorted)):\n    if training_sorted[i].parts[-1]=='08275a5b1c2dfcd739e8c4888a5ee2d29f83eccfa75185404ced1dc0866ea992.png':\n        wbimg=cv2.imread(str(training_sorted[i]))\n        wbimg=cv2.cvtColor(wbimg,cv2.COLOR_BGR2RGB)\n        plt.figure(figsize=(20,8))\n        plt.subplot(1,3,1)\n        plt.imshow(wbimg)\n        plt.title('White background and black foreground')\n        \n        plt.subplot(1,3,2)\n        wbimg=cv2.cvtColor(wbimg,cv2.COLOR_RGB2GRAY)\n        wbimg_canny=cv2.Canny(wbimg,20,100)\n        plt.imshow(wbimg_canny,cmap='gray')\n        plt.title('Canny edge detection')\n        \n        plt.subplot(1,3,3)\n        wbimg_cont=cv2.findContours(wbimg_canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        #Now to create masks with contours\n        wbimg_bg=np.zeros(wbimg.shape)\n        wbimg_mask=cv2.drawContours(wbimg_bg,wbimg_cont,-1,255,-1)\n        \n        #Convolving once\n        wbimg_mask_smooth=signal.convolve2d(np.where(wbimg_mask==255,1,0),np.full((5,5),1/25),'same')\n        wbimg_mask_smooth_thresh=threshold_otsu(wbimg_mask_smooth)\n        wbimg_conv1=np.where(np.where(wbimg_mask_smooth>wbimg_mask_smooth_thresh,0,wbimg_mask_smooth)>0,1,0)\n        \n        #Convolving again\n        wbimg_mask_smooth2=signal.convolve2d(wbimg_conv1,np.full((5,5),1/25),'same')\n        wbimg_mask_smooth_thresh2=threshold_otsu(wbimg_mask_smooth2)\n        wbimg_conv2=np.where(wbimg_mask_smooth2>wbimg_mask_smooth_thresh2,1,0)\n        \n        #Now to create masks with contours after 2 convolutions\n        wbimg_cont=cv2.findContours(cv2.convertScaleAbs(wbimg_conv2),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        wbimg_bg=np.zeros(wbimg.shape)\n        wbimg_mask=cv2.drawContours(wbimg_bg,wbimg_cont,-1,255,-1)\n\n        plt.imshow(wbimg_conv2,cmap='gray')\n        plt.title('Contour detection after 2 convolutions')","metadata":{"_cell_guid":"00861823-ff01-459d-9f6c-46f3846d2c32","_uuid":"6d9962cead595e6a2b55ec748d47a96038aba275","execution":{"iopub.status.busy":"2024-05-16T16:47:08.780765Z","iopub.execute_input":"2024-05-16T16:47:08.781023Z","iopub.status.idle":"2024-05-16T16:47:09.807715Z","shell.execute_reply.started":"2024-05-16T16:47:08.78098Z","shell.execute_reply":"2024-05-16T16:47:09.806698Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#There are some images in the test set with a yellow background and purple foreground\ntest_images = pathlib.Path('../input/stage1_test/').glob('*/images/*.png')\ntesting_sorted=sorted([x for x in test_images])\nfor i in range(len(testing_sorted)):\n    if testing_sorted[i].parts[-1]=='9f17aea854db13015d19b34cb2022cfdeda44133323fcd6bb3545f7b9404d8ab.png':\n        ypimg=cv2.imread(str(testing_sorted[i]))\n        ypimg=cv2.cvtColor(ypimg,cv2.COLOR_BGR2RGB)\n        plt.figure(figsize=(20,8))\n        plt.subplot(1,3,1)\n        plt.imshow(ypimg)\n        plt.title('Yellow background and purple foreground')\n        \n        plt.subplot(1,3,2)\n        ypimg=cv2.cvtColor(ypimg,cv2.COLOR_RGB2GRAY)\n        ypimg_canny=cv2.Canny(ypimg,100,200)\n        plt.imshow(ypimg_canny,cmap='gray')\n        plt.title('Canny edge detection')\n        \n        plt.subplot(1,3,3)\n        ypimg_cont=cv2.findContours(ypimg_canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        #Now to create masks with contours\n        ypimg_bg=np.zeros(ypimg.shape)\n        ypimg_mask=cv2.drawContours(ypimg_bg,ypimg_cont,-1,255,-1)\n        \n        #Convolving once\n        ypimg_mask_smooth=signal.convolve2d(np.where(ypimg_mask==255,1,0),np.full((3,3),1/9),'same')\n        ypimg_mask_smooth_thresh=threshold_otsu(ypimg_mask_smooth)\n        ypimg_conv1=np.where(np.where(ypimg_mask_smooth>ypimg_mask_smooth_thresh,0,ypimg_mask_smooth)>0,1,0)\n        \n        #Convolving again\n        ypimg_mask_smooth2=signal.convolve2d(ypimg_conv1,np.full((3,3),1/9),'same')\n        ypimg_mask_smooth_thresh2=threshold_otsu(ypimg_mask_smooth2)\n        ypimg_conv2=np.where(ypimg_mask_smooth2>ypimg_mask_smooth_thresh2,1,0)\n        \n        #Now to create masks with contours after 2 convolutions\n        ypimg_cont=cv2.findContours(cv2.convertScaleAbs(ypimg_conv2),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]\n        ypimg_bg=np.zeros(ypimg.shape)\n        ypimg_mask=cv2.drawContours(ypimg_bg,ypimg_cont,-1,255,-1)\n\n        plt.imshow(ypimg_conv2,cmap='gray')\n        plt.title('Contour detection after 2 convolutions')","metadata":{"_cell_guid":"94d43655-7d16-48bd-bd22-98dbebd9e883","_uuid":"d8835da0d6152c4d0ebd76b69f519f0d97543378","execution":{"iopub.status.busy":"2024-05-16T16:47:09.809148Z","iopub.execute_input":"2024-05-16T16:47:09.809438Z","iopub.status.idle":"2024-05-16T16:47:09.908477Z","shell.execute_reply.started":"2024-05-16T16:47:09.809383Z","shell.execute_reply":"2024-05-16T16:47:09.907716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It is not hard to see that the same parameters as on the black background and white foreground images will fail miserably on other kinds of images. Can we define these parameters for every kind of image? If yes then we may need to first build a color classifier for us to identify the image before we apply Canny Edge Detection","metadata":{"_cell_guid":"5d1bb6ee-5c3b-4e5c-9219-a097e3f2cbe1","_uuid":"c4e7fe1be0bf725a9beeeec90174341ff01041d9"}},{"cell_type":"markdown","source":"## Pixel Classifier","metadata":{"_cell_guid":"f65c15e4-eff5-4116-b85e-cf6501379773","_uuid":"10107c9139d187518888e093a53792a230d6b7c3"}},{"cell_type":"markdown","source":"Right, so it's time to bring Machine Learning into the perspective. In this section, we'll try to build a pixel classifier that classifies pixels as 0 or 255 depending on the grayscale values of the pixel and its neighbors. ","metadata":{"_cell_guid":"64b37db9-a7d2-4735-8cd4-478e5cbd6cb3","_uuid":"65569cd3512d2db58492cc7d55666d5084ccf108"}},{"cell_type":"code","source":"import os\nprint(os.listdir('/'))","metadata":{"execution":{"iopub.status.busy":"2024-05-16T16:47:09.909671Z","iopub.execute_input":"2024-05-16T16:47:09.909922Z","iopub.status.idle":"2024-05-16T16:47:09.923654Z","shell.execute_reply.started":"2024-05-16T16:47:09.909879Z","shell.execute_reply":"2024-05-16T16:47:09.922675Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!unzip -n ../input/stage1_train.zip -d ../input/stage1_train","metadata":{"execution":{"iopub.status.busy":"2024-05-16T16:47:09.924793Z","iopub.execute_input":"2024-05-16T16:47:09.925127Z","iopub.status.idle":"2024-05-16T16:47:10.949334Z","shell.execute_reply.started":"2024-05-16T16:47:09.925065Z","shell.execute_reply":"2024-05-16T16:47:10.948457Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_path = '../inputs/stage1_train/'\ntest_path = '../inputs/stage1_test/'\ntrain_ids = os.listdir(train_path)\ndef LabelMerge(imgpath):\n    #to get all the png files\n    png_files = [f for f in os.listdir(imgpath) if f.endswith('.png')]\n    #to load the image as a grayscale\n    img = cv2.imread(imgpath+'/'+png_files[0],0)\n    for i in png_files[1:]:\n        temp_img = cv2.imread(imgpath+'/'+i,0)\n        img = img+temp_img\n    return(img)","metadata":{"_cell_guid":"d2be9cd1-6319-4929-a973-1d5f78ad3368","_uuid":"ad412b70698586fa24d40acfde1c15cb4553e84c","execution":{"iopub.status.busy":"2024-05-16T16:47:10.950809Z","iopub.execute_input":"2024-05-16T16:47:10.951091Z","iopub.status.idle":"2024-05-16T16:47:10.964619Z","shell.execute_reply.started":"2024-05-16T16:47:10.951044Z","shell.execute_reply":"2024-05-16T16:47:10.963898Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = train_path+training_sorted[45].parts[-3]+'/masks/'\ncombined_mask=LabelMerge(path)\nplt.imshow(combined_mask,cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('Combined Mask')","metadata":{"_cell_guid":"67c8fa7d-095e-4fc3-9e43-c6fd5c0acfc3","_uuid":"b61fee55295baba71236b90bd207e26f3c711316","execution":{"iopub.status.busy":"2024-05-16T16:47:10.965893Z","iopub.execute_input":"2024-05-16T16:47:10.966258Z","iopub.status.idle":"2024-05-16T16:47:11.137269Z","shell.execute_reply.started":"2024-05-16T16:47:10.966194Z","shell.execute_reply":"2024-05-16T16:47:11.136543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We will use the bounding boxes of the nuclei that we find in our created mask to localize the nuclei in the original image (and use its grayscale values) and use the values of the pixels in the combined mask for that bounding box as labels. \n\nBy using bounding boxes, we are trying to localize regions of interest. It's fine if we have 2-3 nuclei clustered together to form one bounding box (the performance will improve if we are able to sperate them but it shouldn't come at the cost of not detecting a few bounding boxes i.e false negatives) or if we have more bounding boxes than nuclei in the image (false positives). The job of classifying a pixel as 255 or 0 is left to the pixel classifier and in that sense the classifier is only dependent on the grayscale values in the original image and the corresponding labels in the combined mask. We are essentially only looking at those regions that interest us and everything outside these regions is classified as a 0. By including the grayscale values of the neighboring pixels in the dataframe, we are giving the pixel classifier some context. The intuition is that if we find a bounding box in our created mask where there is no nucleus at all, our pixel classifier should set the values of the pixels in that box to 0. What we can't afford though, is to not look at a region where there is a nucleus, because if we do that, then the pixel classifier won't have anything to classify and with our current pipeline, the pixels in that region will be set to 0. The pixel classifier is only as good as the features you define. ","metadata":{"_cell_guid":"7b6c9afe-5aed-44aa-b326-d68bd298fcaa","_uuid":"98e9d20eabb47245817b0986819578e4cb45c795"}},{"cell_type":"code","source":"objects=ndimage.label(canny_mask)[0]\nplt.figure(figsize=(16,8))\nplt.subplot(1,3,1)\nplt.imshow(grayimg[ndimage.find_objects(objects)[20]],cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('Nuclei in the original image')\n\nplt.subplot(1,3,2)\nplt.imshow(canny_mask[ndimage.find_objects(objects)[20]],cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('Created mask')\n\nplt.subplot(1,3,3)\nplt.imshow(combined_mask[ndimage.find_objects(objects)[20]],cmap='gray')\nplt.xticks([])\nplt.yticks([])\nplt.title('Label from the combined mask')","metadata":{"_cell_guid":"39f8b329-08ff-4c2e-8ff5-54e90c2d3988","_uuid":"4d40a2ba19a1d4ebe24b732167358e1ddc937afc","execution":{"iopub.status.busy":"2024-05-16T16:50:27.782884Z","iopub.execute_input":"2024-05-16T16:50:27.783216Z","iopub.status.idle":"2024-05-16T16:50:28.101162Z","shell.execute_reply.started":"2024-05-16T16:50:27.783157Z","shell.execute_reply":"2024-05-16T16:50:28.100336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To get one dataframe for all the pixels within all the bounding boxes in an image\npixels_gs=pd.DataFrame()\ncolumns=[]\nfor i in range(9):\n    columns.append('pixel-'+str(i))\ncolumns=columns+['label']\nbounding=ndimage.find_objects(objects)\nfor bbox in bounding:\n    for i in range(1,canny_mask[bbox].shape[0]-1):\n        for j in range(1,canny_mask[bbox].shape[1]-1):\n            pixel0=grayimg[bbox][i][j] #center pixel\n            pixel1=grayimg[bbox][i-1][j-1] #top left pixel\n            pixel2=grayimg[bbox][i-1][j] #pixel above the center pixel\n            pixel3=grayimg[bbox][i-1][j+1] #top right pixel\n            pixel4=grayimg[bbox][i][j-1] #pixel to the left of center pixel\n            pixel5=grayimg[bbox][i][j+1] #pixel to the right of center pixel\n            pixel6=grayimg[bbox][i+1][j-1] #bottom left pixel\n            pixel7=grayimg[bbox][i+1][j] #pixel to the bottom of center pixel \n            pixel8=grayimg[bbox][i+1][j+1] #bottom right pixel\n            label=combined_mask[i][j] #label of the center pixel\n            neighbors = pd.Series({a:b for (a,b) in zip(columns,[pixel0,pixel1,pixel2,pixel3,pixel4,pixel5,pixel6,pixel7,pixel8,label])})\n            pixels_gs = pixels_gs.append(neighbors, ignore_index=True)","metadata":{"_cell_guid":"1907aa35-c64a-43c5-8b11-d3084a2f94ca","_uuid":"69b355ae2f7f51d52202186de963e11eaf65a2a5","execution":{"iopub.status.busy":"2024-05-16T16:50:32.385904Z","iopub.execute_input":"2024-05-16T16:50:32.386226Z","iopub.status.idle":"2024-05-16T16:53:26.1016Z","shell.execute_reply.started":"2024-05-16T16:50:32.386177Z","shell.execute_reply":"2024-05-16T16:53:26.100885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#To see the head of the dataframe\npixels_gs.head()","metadata":{"_cell_guid":"122ecdff-7bdd-4c8a-8656-c9719c2c9bc0","_uuid":"2298db22d4df1f5c85b581d615448ce32a9cb6a0","scrolled":true,"execution":{"iopub.status.busy":"2024-05-16T16:53:26.102612Z","iopub.execute_input":"2024-05-16T16:53:26.102853Z","iopub.status.idle":"2024-05-16T16:53:26.121711Z","shell.execute_reply.started":"2024-05-16T16:53:26.102812Z","shell.execute_reply":"2024-05-16T16:53:26.120912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pixels_gs['label'].value_counts()","metadata":{"_cell_guid":"10cdd0be-747f-4100-8204-b86386c39284","_uuid":"744e3c2adbd936dd87bfc9ce3ecc18a884ae719a","scrolled":true,"execution":{"iopub.status.busy":"2024-05-16T16:53:26.122963Z","iopub.execute_input":"2024-05-16T16:53:26.123243Z","iopub.status.idle":"2024-05-16T16:53:26.137509Z","shell.execute_reply.started":"2024-05-16T16:53:26.123194Z","shell.execute_reply":"2024-05-16T16:53:26.136841Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Seems like the classes are skewed in favor of 0, contrary to what one would expect when we are only considering pixels from within the bounding boxes. This is probably because our bounding boxes enclose a larger region than just one nuclei","metadata":{"_cell_guid":"6708de3c-e80a-448c-88ff-51b2258e0c05","_uuid":"3532faaf1201ab6345d43225170dae99976e3bc0"}},{"cell_type":"code","source":"#To divide the data into training and testing sets\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.model_selection import train_test_split","metadata":{"_cell_guid":"1c1d2f81-4dc8-426e-a98a-531ff3a8aeaf","_uuid":"f59edc9b8ac0310a99f68ce386ac843f3910d2b3","execution":{"iopub.status.busy":"2024-05-16T16:54:10.867758Z","iopub.execute_input":"2024-05-16T16:54:10.868073Z","iopub.status.idle":"2024-05-16T16:54:10.872617Z","shell.execute_reply.started":"2024-05-16T16:54:10.868028Z","shell.execute_reply":"2024-05-16T16:54:10.871586Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train,X_test,y_train,y_test=train_test_split(pixels_gs.drop('label',axis=1),pixels_gs['label'],test_size=0.3,random_state=101)\nrfc=RandomForestClassifier(n_estimators=100)\nrfc.fit(X_train,y_train)\nrfc_pred=rfc.predict(X_test)\n\nfrom sklearn.metrics import classification_report, confusion_matrix\nprint(confusion_matrix(y_test,rfc_pred))\nprint(classification_report(y_test,rfc_pred))","metadata":{"_cell_guid":"cf44c317-bd4c-4b1d-b028-ba43766e4177","_uuid":"e6b7daed25a1273e234347262f1eaaa5489cd60e","scrolled":true,"execution":{"iopub.status.busy":"2024-05-16T16:54:12.701874Z","iopub.execute_input":"2024-05-16T16:54:12.702187Z","iopub.status.idle":"2024-05-16T16:54:23.766651Z","shell.execute_reply.started":"2024-05-16T16:54:12.702141Z","shell.execute_reply":"2024-05-16T16:54:23.765418Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cv2.__version__","metadata":{"execution":{"iopub.status.busy":"2024-05-16T16:54:43.581845Z","iopub.execute_input":"2024-05-16T16:54:43.582176Z","iopub.status.idle":"2024-05-16T16:54:43.588407Z","shell.execute_reply.started":"2024-05-16T16:54:43.582124Z","shell.execute_reply":"2024-05-16T16:54:43.587468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predicted=np.zeros((canny_mask.shape))\nbbox=[]\nbbox_dim_prod=[0]\nrfc_pred = rfc.predict(pixels_gs.drop('label',axis=1))\nfor i in range(len(bounding)):\n    bbox_dim=np.array(list(background[bounding[i]].shape))-2 #Since we are taking 1 to (n-1) rows and 1 to (n-1) columns\n    bbox_dim_prod.append(np.product(bbox_dim)) #for indexing\n    bbox_pred=rfc_pred[sum(bbox_dim_prod[0:i+1]):sum(bbox_dim_prod[0:i+1])+np.product(bbox_dim)].reshape(bbox_dim[0],bbox_dim[1]) #for reshaping the predicted labels into the reduced dimensions of the bounding box \n    bbox.append(bbox_pred)\n    predicted[bounding[i]][1:predicted[bounding[i]].shape[0]-1,1:predicted[bounding[i]].shape[1]-1]=bbox[i]","metadata":{"_cell_guid":"d9ab496b-a3ba-4565-b10e-dba9e3a011e5","_uuid":"8fc0f7037d52647c70d20b1ad66ec002ea087f41","execution":{"iopub.status.busy":"2024-05-16T16:50:24.500923Z","iopub.status.idle":"2024-05-16T16:50:24.501438Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(13,7))\nplt.subplot(1,2,1)\nplt.imshow(combined_mask,cmap='gray')\nplt.title('Combined Mask')\nplt.xticks([])\nplt.yticks([])\n\nplt.subplot(1,2,2)\nplt.imshow(predicted,cmap='gray')\nplt.title('Predicted Mask')\nplt.xticks([])\nplt.yticks([])","metadata":{"scrolled":true,"_uuid":"f76f59161bd7f20bfb30953348c9732d0e97f47c","execution":{"iopub.status.busy":"2024-05-16T16:50:24.502142Z","iopub.status.idle":"2024-05-16T16:50:24.502478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now the early signs may not be good but the pixel classifier is only as good as the features you define. Furthermore, we have only trained our classifier on the pixels within one image and tested it on the same image which means that there is a possibility that we have overfit. There is also a possibility that we may improve our performance by training the classifier on pixels within bounding boxes from all training images. Taking a 5x5 window may improve our results, distance between the pixel and the center of the nuclei (as defined while contouring) or the relative density of the white pixels (255 or 1s) in the window that we define (window on the canny mask) are some other interesting features. This is going to be the next part of our pipeline.","metadata":{"_uuid":"6279a00b4e56865bbcc6adf00d6fce2ee4eefa2f"}}]}