{"cells":[{"metadata":{"_uuid":"339051976867778429541771c432198ac8d7533f","_cell_guid":"4a40b8f3-5be4-467d-96ce-87033dcf0b66"},"cell_type":"markdown","source":"**Introduction**\n\nThis will be a short exploratory analysis with the goal of becoming more familiar with the 2018 Data Science Bowl dataset and identifying some possible hurdles that could have a negative effect on model performance.\n\n**Contents:**\n- Importing and processing image data\n- Looking at the image metadata summary statistics\n- Plotting image width, height and area distributions\n- Plotting number of nuclei per image distribution\n- Plotting images with the most and fewest nuclei\n- Plotting the smallest and largest nuclei\n- Plotting a sample of images\n- Conclusion"},{"metadata":{"_uuid":"86fa6307b84ecacda64d84ec2aba8a4d657fb82f","collapsed":true,"_cell_guid":"aef7cf21-01f2-4807-80bd-4aea5554e112","trusted":true},"cell_type":"code","source":"import os\nimport sys\nimport random\nimport warnings\n\nimport numpy as np\nimport pandas as pd\nimport math\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nsns.set(color_codes=True)\nimport cv2\n\nfrom tqdm import tqdm\nfrom itertools import chain\nfrom skimage.io import imread, imshow, imread_collection, concatenate_images\nfrom skimage.transform import resize\nfrom skimage.morphology import label\n\nTRAIN_PATH = '../input/stage1_train/'\nTEST_PATH = '../input/stage1_test/'\n\nwarnings.filterwarnings('ignore', category=UserWarning, module='skimage')\nseed = 42\nrandom.seed = seed\nnp.random.seed = seed\n\n# Get train and test IDs\ntrain_ids = next(os.walk(TRAIN_PATH))[1]\ntest_ids = next(os.walk(TEST_PATH))[1]","execution_count":2,"outputs":[]},{"metadata":{"_uuid":"afc5ecb58aaa93f3c519eaff6a24e2fa7f38fb20","_cell_guid":"fc91d64f-2791-4991-aee4-98a9882eaee6","trusted":true},"cell_type":"code","source":"# sys.stdout.flush()\n# These lists will be used to store the images.\nimgs = []\nmasks = []\n\n# These lists will be used to store the image metadata that will then be used to create\n# pandas dataframes.\nimg_data = []\nmask_data = []\nprint('Processing images ... ')\n\n# Loop over the training images. tqdm is used to display progress as reading\n# all the images can take about 1 - 2 minutes.\nfor n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):\n    path = TRAIN_PATH + id_\n    img = imread(path + '/images/' + id_ + '.png')\n    \n    # Get image.\n    imgs.append(img)\n    img_height = img.shape[0]\n    img_width = img.shape[1]\n    img_area = img_width * img_height\n\n    # Initialize counter. There is one mask for each annotated nucleus.\n    nucleus_count = 1\n    \n    # Loop over the mask ids, read the images and gather metadata from them.\n    for mask_file in next(os.walk(path + '/masks/'))[2]:\n        mask = imread(path + '/masks/' + mask_file)\n        masks.append(mask)\n        mask_height = mask.shape[0]\n        mask_width = mask.shape[1]\n        mask_area = mask_width * img_height\n        \n        # Sum and divide by 255 to get the number\n        # of pixels for the nucleus. Masks are grayscale.\n        nucleus_area = (np.sum(mask) / 255)\n        \n        mask_to_img_ratio = nucleus_area / mask_area\n        \n        # Append to masks data list that will be used to create a pandas dataframe.\n        mask_data.append([n, mask_height, mask_width, mask_area, nucleus_area, mask_to_img_ratio])\n        \n        # Increment nucleus count.\n        nucleus_count = nucleus_count + 1\n    \n    # Build image info list that will be used to create dataframe. This is done after the masks loop\n    # because we want to store the number of nuclei per image in the img_data list.\n    img_data.append([img_height, img_width, img_area, nucleus_count])","execution_count":3,"outputs":[]},{"metadata":{"_uuid":"e438c56955d0bf6fdd331124343d31158b860a9d","_cell_guid":"41b8320e-dfb8-410f-b4fa-fcf931a9027f"},"cell_type":"markdown","source":"**Create Images Metadata Dataframe**\n\nCreate a pandas data frame from the list of image metadata that was created in the loop above. This will make the data easier to manipulate and plot.\n\nAfter creating the data frame we can take a look at its summary stats along with the first five and last five rows to make sure it looks ok and get familiar with it."},{"metadata":{"_uuid":"c8fd2c27cd95f166dec9fe50e320b5d46b60a27a","collapsed":true,"_cell_guid":"0286f816-a335-4c61-a652-a63999026a8a","trusted":true},"cell_type":"code","source":"# Create dataframe for images\ndf_img = pd.DataFrame(img_data, columns=['height', 'width', 'area', 'nuclei'])","execution_count":4,"outputs":[]},{"metadata":{"_uuid":"18103d2e1eb56bd4f3b729e2aac76f515931dbf1","_cell_guid":"1ab50c04-7d9a-4bef-91a6-28acf42ca97b","trusted":false,"collapsed":true},"cell_type":"code","source":"df_img.describe()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"014d0dd8ec809adcb73e1540803c73126e63d419","_cell_guid":"0a245fb4-05e4-4cc5-8e74-a6f24090fac8","trusted":false,"collapsed":true},"cell_type":"code","source":"df_img.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"55a33cf2bfc966ce0886e530dc8a0fc66f2295a0","_cell_guid":"d3f807e5-b91e-4c5e-b82a-28128ee41133","trusted":false,"collapsed":true},"cell_type":"code","source":"df_img.tail()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"26e5122437fa30a45b5e8084df2c77465b0019bb","_cell_guid":"718bb880-781d-4d51-b8c4-6abb351fe2d8"},"cell_type":"markdown","source":"**Create Image Masks Metadata Dataframe**\n\nLet's create another data frame for the masks metadata."},{"metadata":{"_uuid":"9a734f69c08f6204ae2cd7247fb533805574d848","collapsed":true,"_cell_guid":"fceaaa18-5d03-49ac-9b3d-6cfe402918de","trusted":true},"cell_type":"code","source":"# Create dataframe for masks\ndf_mask = pd.DataFrame(mask_data, columns=['img_index', 'height', 'width', 'area', 'nucleus_area', 'mask_to_img_ratio'])","execution_count":6,"outputs":[]},{"metadata":{"_uuid":"911073b9df6dd6527162325ed7e68742ec4133e3","_cell_guid":"94157d71-4a3c-4ef2-89f2-15cecdb0e1d9","trusted":false,"collapsed":true},"cell_type":"code","source":"df_mask.describe()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2ca60ddea3b8acd19c1734446de4d28c019c65e4","_cell_guid":"b29ae114-bdff-4877-aec6-f1ef574f9ac5","trusted":false,"collapsed":true},"cell_type":"code","source":"df_mask.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"64779187846cf121a41e973b17f0e890442d5d61","_cell_guid":"818c7360-c419-4b71-b02f-abcf68bda4d4","trusted":false,"collapsed":true},"cell_type":"code","source":"df_mask.tail()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8eb01a91c324b8e768be33fe39222634c96e8e18","_cell_guid":"1cbbf457-a1c8-43e2-bd0f-d5aa5d682540"},"cell_type":"markdown","source":"**Plot some of the metadata distributions**"},{"metadata":{"_uuid":"9e439aa17f744de310e8acc0df89f39cc3dc26bf","_cell_guid":"78df7bfa-32de-4453-8c22-4c6303aeba9c","trusted":false,"collapsed":true},"cell_type":"code","source":"fig, ax = plt.subplots(1, 3, figsize=(10,5))\nwidth_plt = sns.distplot(df_img['width'].values, ax=ax[0])\nwidth_plt.set(xlabel='width (px)')\nwidth_plt.set(ylim=(0, 0.01))\nheight_plt = sns.distplot(df_img['height'].values, ax=ax[1])\nheight_plt.set(xlabel='height (px)')\nheight_plt.set(ylim=(0, 0.015))\narea_plt = sns.distplot(df_img['area'].values)\narea_plt.set(xlabel=\"area (px)\")\nfig.show()\nplt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d043fe1c078ba7814d47d22c0623fdd0324f7df6","_cell_guid":"9c1ff72f-189e-480b-929e-b3c0d6daf7a9"},"cell_type":"markdown","source":"The image dimensions do not appear to be equally distributed and have a somewhat bimodal distribution. This issue will need to be addressed before feeding the images to our model. The images could be resized to squares but this will cause images to be squashed either vertically or horizontally and would result in a loss of information. Most of the images look like they are 256x256 so the squashing/stretching might not be an issue. Scaling the smaller images up might be a better option than scaling the larger images down as this would also result in a loss of information. Different strategies should be tried here to see what works best. "},{"metadata":{"_uuid":"ac5c9a50846e26b4f64ec56801622d9a0c01749c","_cell_guid":"3ffdf2ba-65e3-4a4c-9ce3-484e20eb56de","trusted":false,"collapsed":true},"cell_type":"code","source":"sns.distplot(df_img['nuclei'].values)\nplt.xlabel(\"nuclei\")\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f079ef952a83fec6ed79934a8e59326e0f6b6448","_cell_guid":"f7445aef-0b7e-495f-ad5a-9f3c68c5795b"},"cell_type":"markdown","source":"The distribution of nuclei (masks) per image appears to be skewed to the right. There is quite a large range in the number of nuclei per image. "},{"metadata":{"_uuid":"dfa9b7d211317b1c1b76df76d89330760615f185","_cell_guid":"dbfcd5f2-114c-4f3a-bf26-d44402048e62","trusted":false,"collapsed":true},"cell_type":"code","source":"plt.figure(figsize=(18, 18))\nmuch_nuclei = df_img['nuclei'].argmax()\nprint(much_nuclei)\nplt.grid(None)\nplt.imshow(imgs[much_nuclei])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d05cf1affc2b34247e9f664fdb58a3f734ce152a","_cell_guid":"f3d63f89-dcfc-4540-861d-c83320114e4d"},"cell_type":"markdown","source":"There are 198 annotated nuclei in this image. It's an interesting structure, possibly some kind of creature!"},{"metadata":{"_uuid":"f5355ab1de3ee966f824b85b12be40f8796e019e","_cell_guid":"038092ed-fb56-40d2-9d57-945d89ae0eb6","trusted":false,"collapsed":true},"cell_type":"code","source":"plt.figure(figsize=(18, 18))\nnot_much_nuclei = df_img['nuclei'].argmin()\nprint(df_img['nuclei'].min())\nplt.grid(None)\nplt.imshow(imgs[not_much_nuclei])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ddb9546aa9736fbea609974b0a87e9b983db583a","_cell_guid":"5ec52993-6810-48b6-aaad-c56d91aa6737"},"cell_type":"markdown","source":"There appears to be a lot going on in this image but there are only two annotated nuclei."},{"metadata":{"_uuid":"4169ba42cf7f97475a0ecb84c50ff75bdd5dadca","_cell_guid":"a27d7d1c-a6b8-4aaf-bf3c-a32ea3c63600"},"cell_type":"markdown","source":"**Nuclei Sizes**\n\nLet's take a look at some differently sized nuclei in the training set. "},{"metadata":{"_uuid":"ce015654c6f475ba6b501a9b8e63930986e31d10","_cell_guid":"f8aec08a-04ef-4cd1-91c5-ec119d07ba96","trusted":false,"collapsed":true},"cell_type":"code","source":"smallest_mask_index = df_mask['mask_to_img_ratio'].argmin()\n\nfig, ax = plt.subplots(1, 2, figsize=(16, 16))\nax[0].grid(None)\nax[0].imshow(masks[smallest_mask_index])\nax[1].grid(None)\nax[1].imshow(imgs[df_mask.iloc[[smallest_mask_index], [0]].values[0][0]])\nplt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3cfe84147944a2cbee2ef9815e78a959556fa14e","_cell_guid":"8c4b6ff8-7af3-45f4-988c-f9918d5ebcc7"},"cell_type":"markdown","source":"Wow! This nucleus is either very small or very far away! This makes me concerned about scaling down some of the larger images as some of these small nuclei could become undetectable or a least much more difficult to detect."},{"metadata":{"_uuid":"9dd444151aa13c2a3d88e0b22c647afc9b50a2df","_cell_guid":"2be3b84b-da40-4d91-958f-2304aed42423","trusted":false,"collapsed":true},"cell_type":"code","source":"smallest_mask_resized_128 = resize(masks[smallest_mask_index], (128, 128))\nsmallest_mask_resized_256 = resize(masks[smallest_mask_index], (256, 256))\nsmallest_mask_resized_512 = resize(masks[smallest_mask_index], (512, 512))\nprint(np.sum(smallest_mask_resized_128))\nprint(np.sum(smallest_mask_resized_256))\nprint(np.sum(smallest_mask_resized_512))\nfig, ax = plt.subplots(1, 3, figsize=(14, 14))\nax[0].grid(None)\nax[1].grid(None)\nax[2].grid(None)\nax[0].imshow(smallest_mask_resized_128)\nax[1].imshow(smallest_mask_resized_256)\nax[2].imshow(smallest_mask_resized_512)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"57662893b197d7b0b77d7ea941181ddc34d74f88","_cell_guid":"b856dec3-5dba-47bf-8107-70b84c62269f"},"cell_type":"markdown","source":"As you can see above, the nucleus mask completely disappears when the image is scaled down to 128x128 pixels."},{"metadata":{"_uuid":"887769bdac9de6cc6688b16576ad9b6463c64a20","_cell_guid":"f4142028-4c7d-4f43-834f-b809adadde0d","trusted":true},"cell_type":"code","source":"biggest_mask_index = df_mask['mask_to_img_ratio'].argmax()\nbiggest_mask_img_index = df_mask.iloc[[biggest_mask_index], [0]].values[0][0]\n\nfig, ax = plt.subplots(1, 2, figsize=(12, 12))\nax[0].grid(None)\nax[1].grid(None)\nax[0].imshow(masks[biggest_mask_index])\nax[1].imshow(imgs[biggest_mask_img_index])\nplt.tight_layout()","execution_count":9,"outputs":[]},{"metadata":{"_uuid":"d899b457e39e680cb1b866a2e79e46208b1f78d7","_cell_guid":"d03dd44a-b692-4429-945c-db551f48a4ee"},"cell_type":"markdown","source":"In the image on the right, the nuclei appear to overlap each other. Let's see what the masks look like when stacked on top of each other."},{"metadata":{"_uuid":"60f16cff770f71d209c02bd617b342c01cc7133b","_cell_guid":"3778a7a7-6f90-4bec-8a2f-4a2d3bbd6eca","trusted":true},"cell_type":"code","source":"big_nuclei = df_mask.index[df_mask['img_index'] == biggest_mask_img_index]\nplt.figure(figsize=(18, 18))\nfor i, mask_id in enumerate(big_nuclei):\n    plt.grid(None)\n    plt.imshow(masks[mask_id], interpolation='none', alpha=0.1)","execution_count":10,"outputs":[]},{"metadata":{"_uuid":"e01ee4a6a817ebbff70c1ab16a215e8f749fe357","_cell_guid":"902b5ade-e430-49fb-b85f-6d6958005988"},"cell_type":"markdown","source":"Whereas the nuclei in the image overlap the masks do not appear to do so. "},{"metadata":{"_uuid":"d10aafde02fdf245f594ffec5cfcbf0fb5b3bde7","_cell_guid":"6ab6e0ac-d6ae-4a62-9a54-a512f09b849f","trusted":true},"cell_type":"code","source":"sample_nuclei = df_img.sample(36).index\nfig, ax = plt.subplots(9, 4, figsize=(16, 16))\nrow = 0\ncol = 0\nfor i, img_id in enumerate(sample_nuclei):\n    ax[row, col].grid(False)\n    ax[row, col].imshow(imgs[img_id])\n    \n    # Increment col index and reset each time\n    # it gets to 4 to start a new row\n    col = col + 1\n    if(col == 4):\n        col = 0\n    \n    # Increment row index every 4 items\n    if((i + 1) % 4 == 0):\n        row = row + 1\nplt.tight_layout()","execution_count":14,"outputs":[]},{"metadata":{"_uuid":"ed67090ac5c6383e23a4bb8959ec6f6b7937fc8d","_cell_guid":"67e82bfa-3d34-47d0-9246-9abc355844d0"},"cell_type":"markdown","source":"There is a wide range of different nuclei sizes and shapes."},{"metadata":{"_uuid":"3215ee613be7da96b35703c018dcccc4f9aabe00","_cell_guid":"a86ccaf5-5efa-4e27-bfc4-e2a4c1f1e925"},"cell_type":"markdown","source":"**Conclusion**\n\nThere is a large range of image dimensions in the dataset and not all of the images are square. The smallest image was 256x256 and the largest was 1040x1388 pixels. The smallest nucleus was only a few pixels in size and was found in one of the larger images (1000x1000), resizing this image caused the tiny nucleus to disappear so resizing images should be approached with great caution. The size of nuclei vary a lot throughout the images in the training set and is likely to make detection more challenging. \n\nI have only scratched the surface of this dataset and there is much more to explore. A few more things I would like to look into are the distribution of color in the images, identifying different nuclei groups/clusters and taking a look at the test set.\n\n*Any suggestions for improvements would be very helpful. Also, please don't hesitate to point out any mistakes I might have made (there are probably a lot of them!).*"},{"metadata":{"_uuid":"0e6bddc0c64e23733505acb44fdb135aadbb491e","collapsed":true,"_cell_guid":"60ee2fd9-007e-4f40-83a7-ece2f881da5a"},"cell_type":"markdown","source":"I adapted various parts of the following kernels while putting this together:\n- https://www.kaggle.com/jerrythomas/exploratory-analysis\n- https://www.kaggle.com/keegil/keras-u-net-starter-lb-0-277"}],"metadata":{"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"},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"nbformat":4,"nbformat_minor":1}