{"cells":[{"metadata":{"trusted":true,"_uuid":"c1a6bf14b72e41d2de4212f34bc220e08a5c4c35"},"cell_type":"markdown","source":"# Ship Detection\nThis notebook uses the model trained by [Mask R-CNN Ship Ddetection MVM - 1](https://www.kaggle.com/samlin001/mask-r-cnn-ship-detection-minimum-viable-model-1) to detect ships in satellite images from [Airbus Ship Detection Challenge test_v2.zip](https://www.kaggle.com/c/airbus-ship-detection/data).\n\n"},{"metadata":{"_uuid":"47d0328e464ab04835bf981f3ae8617eb50f82d9"},"cell_type":"markdown","source":"# Project Configuration"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import math\nimport matplotlib\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport os\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport random\nimport skimage.io\nimport sys\nimport time\nimport errno\nfrom skimage.data import imread\nfrom skimage.morphology import label\n\n# Configurations\nPRE_TRAINED_WEIGHT_URL = 'https://github.com/samlin001/Mask_RCNN/releases/download/v2.2-alpha/mask_rcnn_asdc.h5'\nWORKING_DIR = '/kaggle/working'\nINPUT_DIR = '/kaggle/input'\nOUTPUT_DIR = '/kaggle/output'\nIMAGE_DIR = os.path.join(INPUT_DIR, 'test_v2')\nMASK_RCNN_PATH = os.path.join(WORKING_DIR, 'Mask_RCNN-master')\nIMAGE_WIDTH = 768\nIMAGE_HEIGHT = 768\nSHAPE = (IMAGE_WIDTH, IMAGE_HEIGHT)\nCSV_HEADER = 'ImageId,EncodedPixels\\n'\nSUBMISSION_FILE_NAME = os.path.join(WORKING_DIR, 'submission_v2.csv')\n\nprint('Working Dir:', WORKING_DIR, os.listdir(WORKING_DIR))\nprint('Input Dir:', INPUT_DIR, os.listdir(INPUT_DIR))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"534bf07e33f0932b2272d8ab40e7f8e67a9bbdca"},"cell_type":"markdown","source":"# Import Mask R-CNN code\nDownload and import [Mask R-CNN code](https://github.com/samlin001/Mask_RCNN)."},{"metadata":{"trusted":true,"_uuid":"2bf4b1b2e24e9d212210d635f17a15eeb3e11dc0"},"cell_type":"code","source":"# if to clone Mask_R-CNN git when it exists \nUPDATE_MASK_RCNN = False\n\nos.chdir(WORKING_DIR)\nif UPDATE_MASK_RCNN:\n    !rm -rf {MASK_RCNN_PATH}\n\n# Downlaod Mask RCNN code to a local folder \nif not os.path.exists(MASK_RCNN_PATH):\n    ! wget https://github.com/samlin001/Mask_RCNN/archive/master.zip -O Mask_RCNN-master.zip\n    ! unzip Mask_RCNN-master.zip 'Mask_RCNN-master/mrcnn/*'\n    ! rm Mask_RCNN-master.zip\n\n# Import Mask RCNN\nsys.path.append(MASK_RCNN_PATH)  # To find local version of the library\nfrom mrcnn.config import Config\nfrom mrcnn import utils\nimport mrcnn.model as modellib\nfrom mrcnn import visualize\nfrom mrcnn.model import log    ","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"39e7ebcb85fa36dbd5140753e011b77afb3cfa02"},"cell_type":"markdown","source":"## Model configurations for inference\n"},{"metadata":{"trusted":true,"_uuid":"d65d5335db367f346c2b8c71859a102c2ca44884"},"cell_type":"code","source":"class AirbusShipDetectionChallengeGPUConfig(Config):\n    \"\"\"\n    Configuration of Airbus Ship Detection Challenge Dataset \n    Overrides values in the base Config class.\n    From https://github.com/samlin001/Mask_RCNN/blob/master/mrcnn/config.py\n    \"\"\"\n    # https://www.kaggle.com/docs/kernels#technical-specifications\n    NAME = 'ASDC_GPU'\n    # NUMBER OF GPUs to use.\n    GPU_COUNT = 1\n    IMAGES_PER_GPU = 2\n    \n    NUM_CLASSES = 2  # ship or background\n    IMAGE_MIN_DIM = IMAGE_WIDTH\n    IMAGE_MAX_DIM = IMAGE_WIDTH\n    STEPS_PER_EPOCH = 5\n    VALIDATION_STEPS = 5\n    SAVE_BEST_ONLY = True\n    \n    # Minimum probability value to accept a detected instance\n    # ROIs below this threshold are skipped\n    DETECTION_MIN_CONFIDENCE = 0.95\n\n    # Non-maximum suppression threshold for detection\n    DETECTION_NMS_THRESHOLD = 0.05\n\n    \nclass InferenceConfig(AirbusShipDetectionChallengeGPUConfig):\n    # Set batch size to 1 since we'll be running inference on\n    # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU\n    GPU_COUNT = 1\n    # One image for each time at inference\n    IMAGES_PER_GPU = 1\n\nconfig = InferenceConfig()\nconfig.display()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"feab6f12850a7825fdd9f5490eb9b65612e09cde"},"cell_type":"markdown","source":"# Load the weights\n* The weights are the output files on the training of [Mask R-CNN Ship Ddetection MVM-1a](https://www.kaggle.com/samlin001/mask-r-cnn-ship-detection-minimum-viable-model-1a).\n* Importing the weights via \"Add Data\" will copy a few GB data over. It is more efficiency to download [mask_rcnn_asdc.h5](https://github.com/samlin001/Mask_RCNN/releases) from github.\n"},{"metadata":{"trusted":true,"_uuid":"0797a2fa3d5f620b0b4728bd6ce44bda63182b10"},"cell_type":"code","source":"# Create model object in inference mode.\nmodel = modellib.MaskRCNN(mode=\"inference\", model_dir=WORKING_DIR, config=config)\nstart_time = time.time()\npre_weights = PRE_TRAINED_WEIGHT_URL\nweights_path = os.path.join(WORKING_DIR, 'mask_rcnn_asdc.h5')\nif not os.path.exists(weights_path):\n    ! wget {pre_weights} -O {weights_path}\n\nprint(\"Loading weights: \", weights_path)\nmodel.load_weights(weights_path, by_name=True)\nend_time = time.time() - start_time\nprint(\"loading weights: {}\".format(end_time))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"216e602cbe93af42697e9a3783d814fd086726c1"},"cell_type":"markdown","source":"## Mask Run Length Encoding & transformation functions"},{"metadata":{"trusted":true,"_uuid":"05f302bd00de806544e9013d16713cb588e3110b"},"cell_type":"code","source":"# ref: https://github.com/matterport/Mask_RCNN/blob/master/samples/nucleus/nucleus.py\ndef rle_encode(mask):\n    \"\"\"Encodes a mask in Run Length Encoding (RLE).\n    Returns a string of space-separated values.\n    \"\"\"\n    assert mask.ndim == 2, \"Mask must be of shape [Height, Width]\"\n    # Flatten it column wise\n    m = mask.T.flatten()\n    # Compute gradient. Equals 1 or -1 at transition points\n    g = np.diff(np.concatenate([[0], m, [0]]), n=1)\n    # 1-based indicies of transition points (where gradient != 0)\n    rle = np.where(g != 0)[0].reshape([-1, 2]) + 1\n    # Convert second index in each pair to lenth\n    rle[:, 1] = rle[:, 1] - rle[:, 0]\n    return \" \".join(map(str, rle.flatten()))\n\ndef create_submission_records(image_id, masks, scores):\n    \"\"\"Creates submission records.\"\"\"\n    no_masks = len(scores)\n    # Return 'ImageId,' when there is no ship detected\n    if no_masks < 1:\n        return '{},'.format(image_id)\n    \n    # Creates a record for each mask\n    records = []\n    for i in range(no_masks):\n        rle = rle_encode(masks[:,:,i])\n        records.append('{},{}'.format(image_id, rle))\n    return '\\n'.join(records)\n\ndef save_csv(filename, header, records):\n    # Save to csv file\n    content = header + '\\n'.join(records)\n    with open(filename, 'w') as f:\n        f.write(content)\n\n# Load file name list\nfile_names = next(os.walk(IMAGE_DIR))[2]\nprint(len(file_names), ' test images found')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8cc8ca4555013c3f2627ad06c502406b5c8e0223"},"cell_type":"markdown","source":"# Inference\nDetect ships for each image in test_v2.zip and encord their masks into the format for submission."},{"metadata":{"trusted":true,"_uuid":"69e444fa1d9061b07175fd833120f42527d33388"},"cell_type":"code","source":"# set MAX_INFERENCE_IMAGE_NO < len(file_names) for development\nMAX_INFERENCE_IMAGE_NO = 100000\n# MAX_INFERENCE_IMAGE_NO = 10\ninference_start = time.time()\ni = 0\nmask_records = []\nfor image_id in file_names:\n    image = skimage.io.imread(os.path.join(IMAGE_DIR, image_id))\n    # Detect ships\n    result = model.detect([image], verbose=0)[0]\n    records = create_submission_records(image_id, result['masks'], result['scores'])\n    mask_records.append(records)\n    i += 1\n    if i >= MAX_INFERENCE_IMAGE_NO:\n        break\ninference_end = time.time()\ninference_time = inference_end - inference_start\nprint('Inference Time: {:0.2f} minutes for {} images'.format(inference_time/60, i))\nsave_csv(SUBMISSION_FILE_NAME,CSV_HEADER, mask_records)\nprint('Detect {:0.2f} images per second'.format(inference_time/i))\nsave_csv(SUBMISSION_FILE_NAME,CSV_HEADER, mask_records)\n\nprint('Save submission to {}'.format(SUBMISSION_FILE_NAME))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"cb88972255cd987ee8b8f61e87c1fed2a4eafa86"},"cell_type":"markdown","source":"# Result inspections\nDisplay a few ship deticion examples from the results."},{"metadata":{"trusted":true,"_uuid":"04e187e2320728bd8d2a7e6a81150d720e5534c2"},"cell_type":"code","source":"# Read mask encording from the input CSV file \nmasks = pd.read_csv(SUBMISSION_FILE_NAME)\nmasks.head()\n\ndef rle_decode(mask_rle, shape=SHAPE):\n    '''\n    mask_rle: run-length as string formated: [start0] [length0] [start1] [length1]... in 1d array\n    shape: (height,width) of array to return \n    Returns numpy array according to the shape, 1 - mask, 0 - background\n    '''\n    s = mask_rle.split()\n    # gets starts & lengths 1d arrays \n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0::2], s[1::2])]\n    starts -= 1\n    # gets ends 1d array\n    ends = starts + lengths\n    # creates blank mask image 1d array\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    # sets mark pixles\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    # reshape as a 2d mask image\n    return img.reshape(shape).T  # Needed to align to RLE direction\n\ndef masks_as_image(in_mask_list, shape=SHAPE):\n    '''Take the individual ship masks and create a single mask array for all ships\n    in_mask_list: pd Series: [idx0] [RLE string0]...\n    Returns numpy array as (shape.h, sahpe.w, 1)\n    '''\n    all_masks = np.zeros(shape, dtype = np.int16)\n    # if isinstance(in_mask_list, list):\n    for mask in in_mask_list:\n        if isinstance(mask, str):\n            all_masks += rle_decode(mask)\n    return np.expand_dims(all_masks, -1)\n\ndef show_image_mask(image_id, path):\n    '''Show image & ship mask\n    '''\n    fig, axarr = plt.subplots(1, 3, figsize = (20, 5))\n    # image\n    img_0 = imread(os.path.join(path, image_id))\n    axarr[0].imshow(img_0)\n    axarr[0].set_title(image_id)\n    \n    # input mask\n    rle_1 = masks.query('ImageId==\"{}\"'.format(image_id))['EncodedPixels']\n    img_1 = masks_as_image(rle_1)\n    # takes 2d array (shape.h, sahpe.w)\n    axarr[1].imshow(img_1[:, :, 0])\n    axarr[1].set_title('Ship Mask')\n    \n    axarr[2].imshow(img_0)\n    axarr[2].imshow(img_1[:, :, 0], alpha=0.3)\n    axarr[2].set_title('Encoded & Decoded Mask')\n    plt.show()\n\n# inspect a few examples\nshow_image_mask('c175e03b9.jpg', IMAGE_DIR)    \nshow_image_mask('8a56c9bdd.jpg', IMAGE_DIR)\nshow_image_mask('f52f4a484.jpg', IMAGE_DIR)\nfor i in range(20):\n    show_image_mask(random.choice(file_names), IMAGE_DIR)\n    ","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"34109b5b7b4a3bb392752a341d210a24f6787d5a"},"cell_type":"markdown","source":"# Clean up"},{"metadata":{"trusted":true,"_uuid":"152d9d0d8332c87958ebe6ab95e9751ba06c97ec"},"cell_type":"code","source":"!rm -rf {MASK_RCNN_PATH}","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}