{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# TF2 Object Detection on Amazon SageMaker - Data preparation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#If you're using an Amazon SageMaker Notebook instance, please select the \"conda_tensorflow2_p36\" kernel.\n", "#If you're using anything else feel free to make use of the script in 0_set_up_env folder and select the custom kernel." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install -q --upgrade pip\n", "!pip install -q sagemaker==2.96.0\n", "!pip install -q jsonlines\n", "# Get dataset_util file from TF2 Object Detection GitHub repository\n", "!wget -P ./docker/code/utils https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/utils/dataset_util.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import boto3\n", "import jsonlines\n", "import sagemaker\n", "import numpy as np\n", "from PIL import Image\n", "from itertools import cycle, islice\n", "import matplotlib.pyplot as plt\n", "import matplotlib.patches as patches\n", "from sagemaker.processing import Processor, ProcessingInput, ProcessingOutput\n", "\n", "sagemaker_session = sagemaker.Session()\n", "\n", "# we are using the notebook instance role for training in this example\n", "role = sagemaker.get_execution_role() \n", "\n", "# you can specify a bucket name here, we're using the default bucket of SageMaker\n", "bucket = sagemaker_session.default_bucket() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<a name=\"introduction\"></a>\n", "## Get data\n", "In this workshop we will use a dataset from the [inaturalist.org](inaturalist.org) This dataset contains 500 images of bees that have been uploaded by inaturalist users for the purposes of recording the observation and identification. We only used images that their users have licensed under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) license. For your convenience, we have placed the dataset in S3 in a single zip archive here: https://tf2-object-detection.s3-eu-west-1.amazonaws.com/data/bees/dataset.zip\n", "\n", "First, download and unzip the archive." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "!wget -O /tmp/dataset.zip https://tf2-object-detection.s3-eu-west-1.amazonaws.com/data/bees/input/dataset.zip\n", "!mkdir data\n", "!unzip -qo /tmp/dataset.zip -d data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The archive contains the following structure: \n", "- 500 `.jpg` image files \n", "- A \"output.manifest\" file (to be explained later) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's upload this dataset to your own S3 bucket in preparation for labeling and training using Amazon SageMaker." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "prefix = 'data/bees/raw'\n", "s3_input = sagemaker_session.upload_data('data', bucket, prefix)\n", "print(s3_input)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualize labelled images" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's plot all the annotated images. First, let's define a function that displays the local image file and draws over it the bounding boxes obtained via labeling." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def show_annotated_image(img_path, bboxes):\n", " im = np.array(Image.open(img_path), dtype=np.uint8)\n", " \n", " # Create figure and axes\n", " fig,ax = plt.subplots(1)\n", "\n", " # Display the image\n", " ax.imshow(im)\n", "\n", " colors = cycle(['w', 'g', 'b', 'y', 'c', 'm', 'k', 'r'])\n", " \n", " for bbox in bboxes:\n", " # Create a Rectangle patch\n", " rect = patches.Rectangle((bbox['left'],bbox['top']),bbox['width'],bbox['height'],linewidth=2,edgecolor=next(colors),facecolor='none')\n", "\n", " # Add the patch to the Axes\n", " ax.add_patch(rect)\n", "\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, read the augmented manifest (JSON lines format) line by line and display the first 10 images." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "labeling_job_name = 'bees-500'\n", "augmented_manifest_file = 'data/output.manifest'\n", "\n", "with jsonlines.open(augmented_manifest_file, 'r') as reader:\n", " for desc in islice(reader, 10):\n", " img_url = desc['source-ref']\n", " img_file = f'data/{os.path.basename(img_url)}'\n", " bboxes = desc[labeling_job_name]['annotations']\n", " show_annotated_image(img_file, bboxes)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will then process the object detection labels output from Ground Truth (i.e: The output manifest file) and generate TFrecords files to be used during the model training" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Build and push data processing container" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we're following the bring your own paradigm of SageMaker, the first step is to build a docker container (enabling you to chose the base docker image and libraries to be installed) and push the docker to Amazon ECR so it's accessible by Amazon SageMaker " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image_name = 'tfrecord-processing'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "#The following command takes a few minutes to complete\n", "!sh ./docker/build_and_push.sh $image_name" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get the ECR image uri\n", "with open (os.path.join('docker', 'ecr_image_fullname.txt'), 'r') as f:\n", " container = f.readlines()[0][:-1]\n", " \n", "print(container)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Launch SageMaker processing job to generate TFrecords" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data_processor = Processor(\n", " role=role, \n", " image_uri=container, \n", " instance_count=1, \n", " instance_type='ml.m5.xlarge',\n", " volume_size_in_gb=30, \n", " max_runtime_in_seconds=1200,\n", " base_job_name='tf2-object-detection'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "input_folder = '/opt/ml/processing/input'\n", "ground_truth_manifest = '/opt/ml/processing/input/output.manifest'\n", "label_map = '{\"0\": \"bee\"}' # class mapping here - e.g. - each class ID should map to the human readable equivalent\n", "output_folder = '/opt/ml/processing/output'\n", "\n", "data_processor.run(\n", " arguments= [\n", " f'--input={input_folder}',\n", " f'--ground_truth_manifest={ground_truth_manifest}',\n", " f'--label_map={label_map}',\n", " f'--output={output_folder}'\n", " ],\n", " inputs = [\n", " ProcessingInput(\n", " input_name='input',\n", " source=s3_input,\n", " destination=input_folder\n", " )\n", " ],\n", " outputs= [\n", " ProcessingOutput(\n", " output_name='tfrecords',\n", " source=output_folder,\n", " destination=f's3://{bucket}/data/bees/tfrecords'\n", " )\n", " ]\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "conda_tensorflow2_p36", "language": "python", "name": "conda_tensorflow2_p36" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.13" } }, "nbformat": 4, "nbformat_minor": 4 }