{"cells":[{"metadata":{},"cell_type":"markdown","source":"This notebook provides a function decodeToBinaryMask to reconstruct the mask from an encoded string\n\nTwo motivations for this notebook:\n\n1. Wanting to check that the encoded masks really contain the mask information I expect - the proof is being able to reconstruct the mask out of the RLE encoded string.\n\n2. There are some masks in the provided \"sample_submission.csv\" an I was curious what was in them.  They turn out to be empty (i.e. all zero) masks in the size of the images"},{"metadata":{},"cell_type":"markdown","source":"Acknowledgment:\nThe frame of this notebook comes from here (Thanks Tito !) https://www.kaggle.com/its7171/mmdetection-for-segmentation-inference"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"\n!pip install -q \"/kaggle/input/pycocotools/pycocotools-2.0-cp37-cp37m-linux_x86_64.whl\"\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport cv2 \nfrom tqdm import tqdm\nimport pickle\nfrom itertools import groupby\nfrom pycocotools import mask as mutils\nfrom pycocotools import _mask as coco_mask\nimport matplotlib.pyplot as plt\nimport os\nimport base64\nimport typing as t\nimport zlib\nimport random\nrandom.seed(0)\n\nexp_name = \"v4\"\n\nROOT = '../input/hpa-single-cell-image-classification/'\ntrain_or_test = 'test'\ndf = pd.read_csv(os.path.join(ROOT, 'sample_submission.csv'))\nif len(df) == 559:\n    debug = True\n    df = df[:5]\nelse:\n    debug = False","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n# helper funcs"},{"metadata":{"trusted":true},"cell_type":"code","source":"def encode_binary_mask(mask: np.ndarray) -> t.Text:\n  \"\"\"Converts a binary mask into OID challenge encoding ascii text.\"\"\"\n\n  # check input mask --\n  if mask.dtype != np.bool:\n    raise ValueError(\n        \"encode_binary_mask expects a binary mask, received dtype == %s\" %\n        mask.dtype)\n\n  mask = np.squeeze(mask)\n  if len(mask.shape) != 2:\n    raise ValueError(\n        \"encode_binary_mask expects a 2d mask, received shape == %s\" %\n        mask.shape)\n\n  # convert input mask to expected COCO API input --\n  mask_to_encode = mask.reshape(mask.shape[0], mask.shape[1], 1)\n  mask_to_encode = mask_to_encode.astype(np.uint8)\n  mask_to_encode = np.asfortranarray(mask_to_encode)\n\n  # RLE encode mask --\n  encoded_mask = coco_mask.encode(mask_to_encode)[0][\"counts\"]\n\n  # compress and base64 encoding --\n  binary_str = zlib.compress(encoded_mask, zlib.Z_BEST_COMPRESSION)\n  base64_str = base64.b64encode(binary_str)\n  return base64_str.decode()\n\ndef read_img(image_id, color, train_or_test='train', image_size=None):\n    filename = f'{ROOT}/{train_or_test}/{image_id}_{color}.png'\n    assert os.path.exists(filename), f'not found {filename}'\n    img = cv2.imread(filename, cv2.IMREAD_UNCHANGED)\n    if image_size is not None:\n        img = cv2.resize(img, (image_size, image_size))\n    if img.dtype == 'uint16':\n        img = (img/256).astype('uint8')\n    return img\n\ndef load_RGBY_image(image_id, train_or_test='train', image_size=None):\n    red = read_img(image_id, \"red\", train_or_test, image_size)\n    green = read_img(image_id, \"green\", train_or_test, image_size)\n    blue = read_img(image_id, \"blue\", train_or_test, image_size)\n    # using rgb only here\n    #yellow = read_img(image_id, \"yellow\", train_or_test, image_size)\n    stacked_images = np.transpose(np.array([red, green, blue]), (1,2,0))\n    return stacked_images\n\ndef print_masked_img(image_id, mask):\n    img = load_RGBY_image(image_id, train_or_test)\n    \n    plt.figure(figsize=(15, 15))\n    plt.subplot(1, 3, 1)\n    plt.imshow(img)\n    plt.title('Image')\n    plt.axis('off')\n    \n    plt.subplot(1, 3, 2)\n    plt.imshow(mask)\n    plt.title('Mask')\n    plt.axis('off')\n    \n    plt.subplot(1, 3, 3)\n    plt.imshow(img)\n    plt.imshow(mask, alpha=0.6)\n    plt.title('Image + Mask')\n    plt.axis('off')\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The following function decodeToBinaryMask regenerates the mask from the RLE encoded string:"},{"metadata":{"trusted":true},"cell_type":"code","source":"def decodeToBinaryMask(rleCodedStr, imWidth, imHeight):\n    uncodedStr = base64.b64decode(rleCodedStr)\n    uncompressedStr = zlib.decompress(uncodedStr,wbits = zlib.MAX_WBITS)   \n    detection ={\n        'size': [imWidth, imHeight],\n        'counts': uncompressedStr\n    }\n    detlist = []\n    detlist.append(detection)\n    mask = coco_mask.decode(detlist)\n    binaryMask = mask.astype('bool') \n    return binaryMask\n   ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"So let's use the function and decode those masks from the sample_submission.\nNote that the first three are the same and the fourth is different because the fourth image has a different size.\nIn showing the masks on the images however, you can see they are all empty."},{"metadata":{"trusted":true},"cell_type":"code","source":"out_image_dir = f'../work/mmdet_{exp_name}_{train_or_test}/'\n!mkdir -p {out_image_dir}\n\nfor idx in range(len(df)):\n    image_id = df.iloc[idx].ID\n    image_width =  df.iloc[idx].ImageWidth\n    image_height =  df.iloc[idx].ImageHeight\n    PredString = df.iloc[idx].PredictionString\n    \n    # The predictionString starts with label and confidence fields\n    # so split the field contents on space to leave only the RLE string\n    chunks = PredString.split(' ')\n    print(idx)\n    print(chunks)\n    codedStr = chunks[2]\n\n    # Now we can uncode it\n    binary_mask = decodeToBinaryMask(codedStr, image_width, image_height )\n\n    # showing it has a plausible form\n    print(binary_mask.shape)\n    print(binary_mask.dtype)\n\n    \n    # And just to prove we have an inverse function\n    # let's convert it back to a coded string\n    recodedStr =  encode_binary_mask(binary_mask) \n    print(recodedStr)\n    \n    # and let's display the mask now against the image\n    print_masked_img(image_id, binary_mask)    \n       \n    # When we display the masks they look empty \n    # as a further check to prove the mask is really as empty as it looks\n    # findNonZero on the mask returns \"None\", so there are no non-zero pixels\n    intmask = binary_mask.astype('uint8')\n    nz = cv2.findNonZero(intmask)\n    print(nz)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"But that's not very convincing with empty masks (how do we know it's not simply incorrectly displaying the mask ?), \nso now let's try it with a non-trivial mask, a single circle"},{"metadata":{"trusted":true},"cell_type":"code","source":"# set up a black greyscale image \ntestmask = np.zeros((2000,3000), np.uint8)\n\n# draw a circle\ngrayimg = cv2.circle(testmask, (500,500), 200,255, cv2.FILLED )\n\n# check it's shape and type\nprint(grayimg.shape)\nprint(grayimg.dtype)\n\n# get a binary mask of the same size\nbinmask = np.zeros((2000,3000), np.bool8)\n\n# and transfer the circle to the binary mask\nbinmask[grayimg>0]= 1\n\n# check the same and type\nprint(binmask.shape)\nprint(binmask.dtype)\n\n# disply the binary mask\nplt.figure\nplt.imshow(binmask, cmap='gray', vmin=0, vmax=1)\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Now we encode the circle mask:"},{"metadata":{"trusted":true},"cell_type":"code","source":"#Encode the mask to run length encoding\nrle = encode_binary_mask(binmask)\nprint(rle)\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"and now we decode it again and show that it is still a circle:"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Decode the mask from the RLE string\ndecodedMask = decodeToBinaryMask(rle, 2000,3000)\n\n# check it has a plausible form\nprint(decodedMask.shape)\nprint(decodedMask.dtype)\n\n# And show it as an image to prove it's the circle we started with\nshowmask = np.zeros((2000,3000,1), np.uint8)\nshowmask[decodedMask>0]= 1\n\nplt.figure\nplt.imshow(showmask, cmap='gray', vmin=0, vmax=1)\nplt.show()\n","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}