{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "Object Detection & classification",
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "zyBhfd53HCFk",
        "outputId": "60ee2486-4823-4817-f1e1-adf2c7eb8c42"
      },
      "source": [
        "!apt-get install libcairo2-dev libjpeg-dev libgif-dev\n",
        "!pip install pycairo\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "import matplotlib\n",
        "%matplotlib inline"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Reading package lists... Done\n",
            "Building dependency tree       \n",
            "Reading state information... Done\n",
            "libjpeg-dev is already the newest version (8c-2ubuntu8).\n",
            "libcairo2-dev is already the newest version (1.15.10-2ubuntu0.1).\n",
            "libgif-dev is already the newest version (5.1.4-2ubuntu0.1).\n",
            "0 upgraded, 0 newly installed, 0 to remove and 29 not upgraded.\n",
            "Requirement already satisfied: pycairo in /usr/local/lib/python3.7/dist-packages (1.20.0)\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "sFI6vDGWHKk1"
      },
      "source": [
        "import cairo\n",
        "num_imgs = 1000\n",
        "\n",
        "img_size = 32\n",
        "min_object_size = 4\n",
        "max_object_size = 16\n",
        "num_objects = 2\n",
        "\n",
        "bboxes = np.zeros((num_imgs, num_objects, 4))\n",
        "imgs = np.zeros((num_imgs, img_size, img_size, 4), dtype=np.uint8)  # format: BGRA\n",
        "shapes = np.zeros((num_imgs, num_objects), dtype=int)\n",
        "num_shapes = 3\n",
        "shape_labels = ['rectangle', 'circle', 'triangle']\n",
        "colors = np.zeros((num_imgs, num_objects), dtype=int)\n",
        "num_colors = 3\n",
        "color_labels = ['r', 'g', 'b']\n",
        "\n",
        "for i_img in range(num_imgs):\n",
        "    surface = cairo.ImageSurface.create_for_data(imgs[i_img], cairo.FORMAT_ARGB32, img_size, img_size)\n",
        "    cr = cairo.Context(surface)\n",
        "\n",
        "    # Fill background white.\n",
        "    cr.set_source_rgb(1, 1, 1)\n",
        "    cr.paint()\n",
        "    \n",
        "    # TODO: Try no overlap here.\n",
        "    # Draw random shapes.\n",
        "    for i_object in range(num_objects):\n",
        "        shape = np.random.randint(num_shapes)\n",
        "        shapes[i_img, i_object] = shape\n",
        "        if shape == 0:  # rectangle\n",
        "            w, h = np.random.randint(min_object_size, max_object_size, size=2)\n",
        "            x = np.random.randint(0, img_size - w)\n",
        "            y = np.random.randint(0, img_size - h)\n",
        "            bboxes[i_img, i_object] = [x, y, w, h]\n",
        "            cr.rectangle(x, y, w, h)            \n",
        "        elif shape == 1:  # circle   \n",
        "            r = 0.5 * np.random.randint(min_object_size, max_object_size)\n",
        "            x = np.random.randint(r, img_size - r)\n",
        "            y = np.random.randint(r, img_size - r)\n",
        "            bboxes[i_img, i_object] = [x - r, y - r, 2 * r, 2 * r]\n",
        "            cr.arc(x, y, r, 0, 2*np.pi)\n",
        "        elif shape == 2:  # triangle\n",
        "            w, h = np.random.randint(min_object_size, max_object_size, size=2)\n",
        "            x = np.random.randint(0, img_size - w)\n",
        "            y = np.random.randint(0, img_size - h)\n",
        "            bboxes[i_img, i_object] = [x, y, w, h]\n",
        "            cr.move_to(x, y)\n",
        "            cr.line_to(x+w, y)\n",
        "            cr.line_to(x+w, y+h)\n",
        "            cr.line_to(x, y)\n",
        "            cr.close_path()\n",
        "        \n",
        "        # TODO: Introduce some variation to the colors by adding a small random offset to the rgb values.\n",
        "        color = np.random.randint(num_colors)\n",
        "        colors[i_img, i_object] = color\n",
        "        max_offset = 0.3\n",
        "        r_offset, g_offset, b_offset = max_offset * 2. * (np.random.rand(3) - 0.5)\n",
        "        if color == 0:\n",
        "            cr.set_source_rgb(1-max_offset+r_offset, 0+g_offset, 0+b_offset)\n",
        "        elif color == 1:\n",
        "            cr.set_source_rgb(0+r_offset, 1-max_offset+g_offset, 0+b_offset)\n",
        "        elif color == 2:\n",
        "            cr.set_source_rgb(0+r_offset, 0-max_offset+g_offset, 1+b_offset)\n",
        "        cr.fill()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1b22TdRRWW4F"
      },
      "source": [
        "imgs = imgs[..., 2::-1]"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "elXj1uS7WZjK",
        "outputId": "20b97507-b299-4232-eaf6-985cc2c30730"
      },
      "source": [
        "imgs.shape"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(1000, 32, 32, 3)"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 7
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 265
        },
        "id": "D8xyIRNzWp64",
        "outputId": "8d5fbe48-3304-4b65-9d8d-42ecbd97d2c6"
      },
      "source": [
        "i = 3\n",
        "plt.imshow(imgs[i], interpolation='none', origin='lower', extent=[0, img_size, 0, img_size])\n",
        "for bbox, shape, color in zip(bboxes[i], shapes[i], colors[i]):\n",
        "    plt.gca().add_patch(matplotlib.patches.Rectangle((bbox[0], bbox[1]), bbox[2], bbox[3], ec='k', fc='none'))\n",
        "    plt.annotate(shape_labels[shape], (bbox[0], bbox[1] + bbox[3] + 0.7), color=color_labels[color], clip_on=False)\n",
        "# surface.write_to_png(\"circle.png\")"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "TilaovYy5gRI",
        "outputId": "5ca01987-ea9d-4143-b083-8af98f1ab83a"
      },
      "source": [
        "X = (imgs - 128.) / 255.\n",
        "X.shape, np.mean(X), np.std(X)\n"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "((1000, 32, 32, 3), 0.40640019786560416, 0.2643851084254905)"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 9
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "WjPWoR6P5m7m",
        "outputId": "d6f4ad87-0871-42a0-a1ca-b2c815f4fab9"
      },
      "source": [
        "colors_onehot = np.zeros((num_imgs, num_objects, num_colors))\n",
        "for i_img in range(num_imgs):\n",
        "    for i_object in range(num_objects):\n",
        "        colors_onehot[i_img, i_object, colors[i_img, i_object]] = 1\n",
        "\n",
        "shapes_onehot = np.zeros((num_imgs, num_objects, num_shapes))\n",
        "for i_img in range(num_imgs):\n",
        "    for i_object in range(num_objects):\n",
        "        shapes_onehot[i_img, i_object, shapes[i_img, i_object]] = 1\n",
        "        \n",
        "y = np.concatenate([bboxes / img_size, shapes_onehot, colors_onehot], axis=-1).reshape(num_imgs, -1)\n",
        "y.shape, np.all(np.argmax(colors_onehot, axis=-1) == colors)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "((1000, 20), True)"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 10
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "_O6XUxO86b60",
        "outputId": "9bc521d7-6685-4127-ae52-d5621f41f332"
      },
      "source": [
        "y[0]"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "array([0.4375 , 0.15625, 0.40625, 0.3125 , 0.     , 0.     , 1.     ,\n",
              "       0.     , 0.     , 1.     , 0.65625, 0.53125, 0.15625, 0.25   ,\n",
              "       1.     , 0.     , 0.     , 1.     , 0.     , 0.     ])"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 23
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "h3SWVFsY6pDq"
      },
      "source": [
        "i = int(0.8 * num_imgs)\n",
        "train_X = X[:i]\n",
        "test_X = X[i:]\n",
        "train_y = y[:i]\n",
        "test_y = y[i:]\n",
        "test_imgs = imgs[i:]\n",
        "test_bboxes = bboxes[i:]\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "gVoFNlek6uxM"
      },
      "source": [
        "from keras.models import Sequential\n",
        "from keras.layers import Dense, Activation, Dropout, Conv2D, Convolution2D, MaxPooling2D, Flatten\n",
        "\n",
        "filter_size = 3\n",
        "pool_size = 2\n",
        "\n",
        "model = Sequential([\n",
        "        Conv2D(32,5, input_shape=X.shape[1:],  activation='relu'), \n",
        "        MaxPooling2D(pool_size=(pool_size, pool_size)), \n",
        "        Conv2D(64, filter_size, activation='relu'), \n",
        "        MaxPooling2D(pool_size=(pool_size, pool_size)), \n",
        "        Conv2D(128, filter_size, activation='relu'),\n",
        "        Conv2D(128, filter_size, activation='relu'), \n",
        "        Flatten(), \n",
        "        Dropout(0.4), \n",
        "        Dense(256, activation='relu'), \n",
        "        Dropout(0.4), \n",
        "        Dense(y.shape[-1])\n",
        "    ]) \n",
        "\n",
        "model.compile('adadelta', 'mse')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ERIpAdm18Ggc"
      },
      "source": [
        "# Flip bboxes during training.\n",
        "# Note: The validation loss is always quite big here because we don't flip the bounding boxes for the validation data. \n",
        "def IOU(bbox1, bbox2):\n",
        "    '''Calculate overlap between two bounding boxes [x, y, w, h] as the area of intersection over the area of unity'''\n",
        "    x1, y1, w1, h1 = bbox1[0], bbox1[1], bbox1[2], bbox1[3]  # TODO: Check if its more performant if tensor elements are accessed directly below.\n",
        "    x2, y2, w2, h2 = bbox2[0], bbox2[1], bbox2[2], bbox2[3]\n",
        "\n",
        "    w_I = min(x1 + w1, x2 + w2) - max(x1, x2)\n",
        "    h_I = min(y1 + h1, y2 + h2) - max(y1, y2)\n",
        "    if w_I <= 0 or h_I <= 0:  # no overlap\n",
        "        return 0\n",
        "    I = w_I * h_I\n",
        "\n",
        "    U = w1 * h1 + w2 * h2 - I\n",
        "\n",
        "    return I / U\n",
        "\n",
        "def dist(bbox1, bbox2):\n",
        "    return np.sqrt(np.sum(np.square(bbox1[:2] - bbox2[:2])))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ULFtqFOw8Ops"
      },
      "source": [
        "num_epochs_flipping = 1\n",
        "num_epochs_no_flipping = 0  # has no significant effect\n",
        "\n",
        "flipped_train_y = np.array(train_y)\n",
        "flipped = np.zeros((len(train_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "ious_epoch = np.zeros((len(train_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "dists_epoch = np.zeros((len(train_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "mses_epoch = np.zeros((len(train_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "acc_shapes_epoch = np.zeros((len(train_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "acc_colors_epoch = np.zeros((len(train_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "\n",
        "flipped_test_y = np.array(test_y)\n",
        "flipped_test = np.zeros((len(test_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "ious_test_epoch = np.zeros((len(test_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "dists_test_epoch = np.zeros((len(test_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "mses_test_epoch = np.zeros((len(test_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "acc_shapes_test_epoch = np.zeros((len(test_y), num_epochs_flipping + num_epochs_no_flipping))\n",
        "acc_colors_test_epoch = np.zeros((len(test_y), num_epochs_flipping + num_epochs_no_flipping))\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "rr-QEHUK8TzH",
        "outputId": "482d76c2-02a3-496b-beb3-a9aa5b58aacd"
      },
      "source": [
        "# TODO: Calculate ious directly for all samples (using slices of the array pred_y for x, y, w, h).\n",
        "for epoch in range(num_epochs_flipping):\n",
        "    print('Epoch', epoch)\n",
        "    model.fit(train_X, flipped_train_y, epochs=1, validation_data=(test_X, test_y), verbose=2)\n",
        "    pred_y = model.predict(train_X)\n",
        "\n",
        "    for sample, (pred, exp) in enumerate(zip(pred_y, flipped_train_y)):\n",
        "        \n",
        "        # TODO: Make this simpler.\n",
        "        pred = pred.reshape(num_objects, -1)\n",
        "        exp = exp.reshape(num_objects, -1)\n",
        "        \n",
        "        pred_bboxes = pred[:, :4]\n",
        "        exp_bboxes = exp[:, :4]\n",
        "        \n",
        "        ious = np.zeros((num_objects, num_objects))\n",
        "        dists = np.zeros((num_objects, num_objects))\n",
        "        mses = np.zeros((num_objects, num_objects))\n",
        "        for i, exp_bbox in enumerate(exp_bboxes):\n",
        "            for j, pred_bbox in enumerate(pred_bboxes):\n",
        "                ious[i, j] = IOU(exp_bbox, pred_bbox)\n",
        "                dists[i, j] = dist(exp_bbox, pred_bbox)\n",
        "                mses[i, j] = np.mean(np.square(exp_bbox - pred_bbox))\n",
        "                \n",
        "        new_order = np.zeros(num_objects, dtype=int)\n",
        "        \n",
        "        for i in range(num_objects):\n",
        "            # Find pred and exp bbox with maximum iou and assign them to each other (i.e. switch the positions of the exp bboxes in y).\n",
        "            ind_exp_bbox, ind_pred_bbox = np.unravel_index(ious.argmax(), ious.shape)\n",
        "            ious_epoch[sample, epoch] += ious[ind_exp_bbox, ind_pred_bbox]\n",
        "            dists_epoch[sample, epoch] += dists[ind_exp_bbox, ind_pred_bbox]\n",
        "            mses_epoch[sample, epoch] += mses[ind_exp_bbox, ind_pred_bbox]\n",
        "            ious[ind_exp_bbox] = -1  # set iou of assigned bboxes to -1, so they don't get assigned again\n",
        "            ious[:, ind_pred_bbox] = -1\n",
        "            new_order[ind_pred_bbox] = ind_exp_bbox\n",
        "        \n",
        "        flipped_train_y[sample] = exp[new_order].flatten()\n",
        "        \n",
        "        flipped[sample, epoch] = 1. - np.mean(new_order == np.arange(num_objects, dtype=int))#np.array_equal(new_order, np.arange(num_objects, dtype=int))  # TODO: Change this to reflect the number of flips.\n",
        "        ious_epoch[sample, epoch] /= num_objects\n",
        "        dists_epoch[sample, epoch] /= num_objects\n",
        "        mses_epoch[sample, epoch] /= num_objects\n",
        "        \n",
        "        acc_shapes_epoch[sample, epoch] = np.mean(np.argmax(pred[:, 4:4+num_shapes], axis=-1) == np.argmax(exp[:, 4:4+num_shapes], axis=-1))\n",
        "        acc_colors_epoch[sample, epoch] = np.mean(np.argmax(pred[:, 4+num_shapes:4+num_shapes+num_colors], axis=-1) == np.argmax(exp[:, 4+num_shapes:4+num_shapes+num_colors], axis=-1))\n",
        "\n",
        "    \n",
        "    # Calculate metrics on test data. \n",
        "    pred_test_y = model.predict(test_X)\n",
        "    # TODO: Make this simpler.\n",
        "    for sample, (pred, exp) in enumerate(zip(pred_test_y, flipped_test_y)):\n",
        "        \n",
        "        # TODO: Make this simpler.\n",
        "        pred = pred.reshape(num_objects, -1)\n",
        "        exp = exp.reshape(num_objects, -1)\n",
        "        \n",
        "        pred_bboxes = pred[:, :4]\n",
        "        exp_bboxes = exp[:, :4]\n",
        "        \n",
        "        ious = np.zeros((num_objects, num_objects))\n",
        "        dists = np.zeros((num_objects, num_objects))\n",
        "        mses = np.zeros((num_objects, num_objects))\n",
        "        for i, exp_bbox in enumerate(exp_bboxes):\n",
        "            for j, pred_bbox in enumerate(pred_bboxes):\n",
        "                ious[i, j] = IOU(exp_bbox, pred_bbox)\n",
        "                dists[i, j] = dist(exp_bbox, pred_bbox)\n",
        "                mses[i, j] = np.mean(np.square(exp_bbox - pred_bbox))\n",
        "                \n",
        "        new_order = np.zeros(num_objects, dtype=int)\n",
        "        \n",
        "        for i in range(num_objects):\n",
        "            # Find pred and exp bbox with maximum iou and assign them to each other (i.e. switch the positions of the exp bboxes in y).\n",
        "            ind_exp_bbox, ind_pred_bbox = np.unravel_index(mses.argmin(), mses.shape)\n",
        "            ious_test_epoch[sample, epoch] += ious[ind_exp_bbox, ind_pred_bbox]\n",
        "            dists_test_epoch[sample, epoch] += dists[ind_exp_bbox, ind_pred_bbox]\n",
        "            mses_test_epoch[sample, epoch] += mses[ind_exp_bbox, ind_pred_bbox]\n",
        "            mses[ind_exp_bbox] = 1000000#-1  # set iou of assigned bboxes to -1, so they don't get assigned again\n",
        "            mses[:, ind_pred_bbox] = 10000000#-1\n",
        "            new_order[ind_pred_bbox] = ind_exp_bbox\n",
        "        \n",
        "        flipped_test_y[sample] = exp[new_order].flatten()\n",
        "        \n",
        "        flipped_test[sample, epoch] = 1. - np.mean(new_order == np.arange(num_objects, dtype=int))#np.array_equal(new_order, np.arange(num_objects, dtype=int))  # TODO: Change this to reflect the number of flips.\n",
        "        ious_test_epoch[sample, epoch] /= num_objects\n",
        "        dists_test_epoch[sample, epoch] /= num_objects\n",
        "        mses_test_epoch[sample, epoch] /= num_objects\n",
        "        \n",
        "        acc_shapes_test_epoch[sample, epoch] = np.mean(np.argmax(pred[:, 4:4+num_shapes], axis=-1) == np.argmax(exp[:, 4:4+num_shapes], axis=-1))\n",
        "        acc_colors_test_epoch[sample, epoch] = np.mean(np.argmax(pred[:, 4+num_shapes:4+num_shapes+num_colors], axis=-1) == np.argmax(exp[:, 4+num_shapes:4+num_shapes+num_colors], axis=-1))\n",
        "       \n",
        "            \n",
        "    print('Flipped {} % of all elements'.format(np.mean(flipped[:, epoch]) * 100.))\n",
        "    print('Mean IOU: {}'.format(np.mean(ious_epoch[:, epoch])))\n",
        "    print('Mean dist: {}'.format(np.mean(dists_epoch[:, epoch])))\n",
        "    print('Mean mse: {}'.format(np.mean(mses_epoch[:, epoch])))\n",
        "    print('Accuracy shapes: {}'.format(np.mean(acc_shapes_epoch[:, epoch])))\n",
        "    print('Accuracy colors: {}'.format(np.mean(acc_colors_epoch[:, epoch])))\n",
        "    \n",
        "    print('--------------- TEST ----------------')\n",
        "    print('Flipped {} % of all elements'.format(np.mean(flipped_test[:, epoch]) * 100.))\n",
        "    print('Mean IOU: {}'.format(np.mean(ious_test_epoch[:, epoch])))\n",
        "    print('Mean dist: {}'.format(np.mean(dists_test_epoch[:, epoch])))\n",
        "    print('Mean mse: {}'.format(np.mean(mses_test_epoch[:, epoch])))\n",
        "    print('Accuracy shapes: {}'.format(np.mean(acc_shapes_test_epoch[:, epoch])))\n",
        "    print('Accuracy colors: {}'.format(np.mean(acc_colors_test_epoch[:, epoch])))\n",
        "    print()"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Epoch 0\n",
            "25/25 - 33s - loss: 0.2424 - val_loss: 0.2394\n",
            "Flipped 0.0 % of all elements\n",
            "Mean IOU: 0.0\n",
            "Mean dist: 0.5303613788074256\n",
            "Mean mse: 0.1333769995181736\n",
            "Accuracy shapes: 0.344375\n",
            "Accuracy colors: 0.31\n",
            "--------------- TEST ----------------\n",
            "Flipped 49.0 % of all elements\n",
            "Mean IOU: 0.0\n",
            "Mean dist: 0.5323391392244374\n",
            "Mean mse: 0.13168510682584525\n",
            "Accuracy shapes: 0.31\n",
            "Accuracy colors: 0.325\n",
            "\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 949
        },
        "id": "avZAedkX_NfQ",
        "outputId": "e8ab82f5-0c90-47b7-85e7-192995c4b962"
      },
      "source": [
        "# model.layers\n",
        "weights = model.layers[0].get_weights()[0]\n",
        "weights = weights.transpose(3, 0, 1, 2)\n",
        "print(weights.shape)\n",
        "# plt.imshow(weights[0] * 255. + 128., interpolation='none', origin='lower')\n",
        "print(np.mean(weights[0]), np.std(weights[0]), np.min(weights[0]), np.max(weights[0]))\n",
        "adj_weights = (weights * 255.) + 128.\n",
        "print(np.mean(adj_weights[0]), np.std(adj_weights[0]), np.min(adj_weights[0]), np.max(adj_weights[0]))\n",
        "plt.figure(figsize=(16, 8))\n",
        "for i in range(24):\n",
        "    plt.subplot(4, 6, i+1)\n",
        "    plt.imshow(adj_weights[i, :, :], interpolation='none', origin='lower', cmap='Greys')"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "(32, 5, 5, 3)\n",
            "0.0071782973 0.045646656 -0.08047159 0.08199458\n",
            "129.83046 11.639896 107.479744 148.90862\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n",
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 1152x576 with 24 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 300
        },
        "id": "gs3CoEUQAAER",
        "outputId": "6dce11dc-c057-4eda-86dd-4518f66699cc"
      },
      "source": [
        "plt.pcolor(flipped[:1000], cmap='Greys', vmax=1.)\n",
        "# plt.axvline(num_epochs_flipping, c='r')\n",
        "plt.xlabel('Epoch')\n",
        "plt.ylabel('Training sample')"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "Text(0, 0.5, 'Training sample')"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 17
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3G7WkFCiADlB",
        "outputId": "161e76b1-f215-4cdb-ce88-7eb8564648e1"
      },
      "source": [
        "\n",
        "pred_y = model.predict(test_X)\n",
        "pred_y = pred_y.reshape(len(pred_y), num_objects, -1)\n",
        "pred_bboxes = pred_y[..., :4] * img_size\n",
        "pred_shapes = np.argmax(pred_y[..., 4:4+num_shapes], axis=-1).astype(int)  # take max from probabilities\n",
        "# print pred_y[..., 4+num_shapes:4+num_shapes+num_colors].shape\n",
        "# print np.argmax(pred_y[..., 5:8], axis=-1).shape\n",
        "pred_colors = np.argmax(pred_y[..., 4+num_shapes:4+num_shapes+num_colors], axis=-1).astype(int)\n",
        "pred_bboxes.shape, pred_shapes.shape, pred_colors.shape"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "((200, 2, 4), (200, 2), (200, 2))"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 18
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "p0NOPbY8AKBS",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 479
        },
        "outputId": "39250029-6816-45f6-d05d-3e2f7f9d174d"
      },
      "source": [
        "plt.figure(figsize=(16, 8))\n",
        "for i_subplot in range(1, 9):\n",
        "    plt.subplot(2, 4, i_subplot)\n",
        "    i = np.random.randint(len(test_X))\n",
        "    plt.imshow(test_imgs[i], interpolation='none', origin='lower', extent=[0, img_size, 0, img_size])\n",
        "    for bbox, shape, color in zip(pred_bboxes[i], pred_shapes[i], pred_colors[i]):\n",
        "        plt.gca().add_patch(matplotlib.patches.Rectangle((bbox[0], bbox[1]), bbox[2], bbox[3], ec='k', fc='none'))\n",
        "        plt.annotate(shape_labels[shape], (bbox[0], bbox[1] + bbox[3] + 0.7), color=color_labels[color], clip_on=False, bbox={'fc': 'w', 'ec': 'none', 'pad': 1, 'alpha': 0.6})"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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Q+nllzdHGrfZpqlvvXX76yxf3gycd93VPPfQQx+574gjmlN499gjWbWqqV10UfO6CxYkT31qctBB//o1f/KTZjDdf//m9gMPNEc/YQQMngAAMCybb958rTX58IeT44579P1f+MLEXuctb2k+97nnnsnSpckFFzxy36abPnK91on3VmtzGu4ZZ0z8OTAkPuMJAADDduihySmnPPIZyptvbk6BPfjg5BvfSG6/vanfcUfzdcstkzVrHnn+mjXJdtslDz6YnHba+Nvbf//krLOaz3quWvXoQfVh++6b/OhHybXXNrfvvTe55pop7yJMhiOeAAAwbC97WXLVVY+cyrrFFsnXvpY85znJRz+aHHBAstFGzam4S5cmr3td85nO//bfms+GfupTyYtelCxc2Hxdfyhtc+SRyXnnNaff7rhjs3jQE5/46McsXNhs6+ijk/vvb2qf/nTyrGcNe+/hXzF4AgDAENz3lKc0n8182PHHN5fHOuaY5rK+/fd/9O/xfPvbm8tjLV366NsPH1FdsCD58z9vBtzbb0/22Sd53vOa+9Y/+nnwwcnPfjbRXYKhMXgCAMBccPjhzWJEDzyQ/MmfNIsMQU8YPAEAYHqOS5I/+7M/W/Ka17xm5rpo+1wn9ITFhQAAAOiUwRMAAIBOGTyB/+sjH/nITLcAjO1LMgrAbGXwBAAAoFMGT2CovvnNR68GP0wHHpgsW9bNa8O8IKAAzBCDJ7BBtSbr1k388V1+Xws8hoACMEsYPIF/5frrk912S9785uS5z00+9ankhS9Mnv/85IQTHnncV77S1PbcM3nTm5If/zg555zkgx9M9torue665Mtfbp67557JkUcm993XPPctb0ne857kxS9OnvGM5Mwzm/q6dck73pHsvntyyCHJYYc9ct/6vve9ZL/9khe8IPmjP3rk92fDnCegAMxCfo8n0GrFiuTUU5O7726+r7zkkubgyitfmVx4YfLkJyef/nTzvew22yR33JFsvXVz/+GHJ0cd1bzOVlslb3tbc/1jH0tOPjl597ub2ytXJhddlPz6183zjjoqOfvs5vvqK69MVq9Onv3s5NhjH93bbbc12/7+95PNN08++9nkxBOTj398ZH88MLMEFIBZxuAJtNppp2TffZMPfKA5eLH33k39nnua73kvv7w5kLHNNk19663bX2f58ub72bvuap576KGP3HfEEcmCBckeeySrVjW1iy5qXnfBguSpT00OOuhfv+ZPftJ837v//s3tBx5oDq7AvCGgAMwyBk+g1eabN19rTT784eS44x59/xe+MLHXectbmo+V7blnsnRpcsEFj9y36aaPXK914r3V2pzld8YZE38OzCkCCsAsM63PeJZSXl5KubqUcm0p5UPDagoYjmFk9NBDk1NOeeQjWjff3Jxhd/DByTe+kdx+e1O/447m65ZbJmvWPPL8NWuS7bZLHnwwOe208be3//7JWWc1HyVbterR3wc/bN99kx/9KLn22ub2vfcm11wzlb2DmTXtjAoodMr3ujA8Ux48SykbJfnrJH+YZI8kR5dS9hhWY8D0DCujL3tZ8vrXN2fKPe95zce81qxJnvOc5KMfTQ44oDlY8r73NY9/3euSz32uOfPvuuuadU9e9KLm+9Xddx9/e0cemeywQ3N23xvf2KxN8sQnPvoxCxc2B2eOPrpZO2W//ZqPocFsMpWM3nrrrTnttNNyx5135vTTTxdQ6JDvdWG4Sp3M6TPrP7GU/ZJ8otZ66OD2h5Ok1vqfx3rO4sWL6zK/44t5bvHixVm2bFnpejtTyeiXvvSluuWWW+b1r3991+1t0D33JFts0Rys2Wef5uDJU586oy0xj/Q4o186++yzl7zmNa9pbnzpSznusafYjoKAdu5zr+j87dc7H/zOxL8f7XFGfa8LGTuj0/mM5/ZJblzv9k1JXjSN1wOGa9Zm9PDDm7VOHngg+ZM/8T0tc9bszKiAMn/MzoxCT3W+uFApZUmSJYOb95dSlne9zZ7ZJsltM93ECM23/U0mv887ddXIVDw2o0mWv+ENb5jBjh7tP/yH5tKh+faenW/7m8zBjD583x//8R/PSE//V/cB9X6dH7b5T6XMmYzOs+915+X7NfZ5PK0Znc7geXOSHde7vcOg9ii11pOSnJQkpZRltdbF09jmrDPf9nm+7W/S632W0QmYb/s83/Y36fU+y+g45tv+Jva5Z2R0HPNtfxP7PB3TWdX2Z0l2LaU8vZTyuCSvS3LOdBsChkZGod9kFPpNRmGIpnzEs9a6tpTyriT/kGSjJKfUWq8YWmfAtMgo9JuMQr/JKAzXtD7jWWv9bpLvTuIpJ01ne7PUfNvn+ba/SY/3WUYnZL7t83zb36TH+yyj45pv+5vY516R0XHNt/1N7POUTfnXqQAAAMBETOczngAAADCukQyepZSXl1KuLqVcW0r50Ci2OWqllFNKKavXX0K7lLJ1KeXcUsqKwdcnzWSPw1ZK2bGUcn4p5cpSyhWllOMH9Tm536WUx5dSLimlXD7Y308O6k8vpfx08P7++mABgllFRufWe/VhMiqjs4mMyqiM9tt8y+h8y2fSfUY7HzxLKRsl+eskf5hkjyRHl1L26Hq7M2Bpkpc/pvahJOfVWndNct7g9lyyNsn7a617JNk3yTsHf7dzdb/vT3JwrXXPJHsleXkpZd8kn03y+VrrM5PcmeStM9jjpMnonHyvPkxGZXQ2WRoZlVEZ7bOlmV8ZnW/5TDrO6CiOeO6T5Npa629qrQ8k+ZskrxrBdkeq1nphkjseU35VklMH109NcsRIm+pYrXVlrfXng+trklyVZPvM0f2ujXsGNzcZXGqSg5OcOajPxv2V0cZs/LvbIBmV0dlERmV0UJ+N+yujjdn4dzem+ZbPpPuMjmLw3D7JjevdvmlQmw+2rbWuHFy/Jcm2M9lMl0opOyfZO8lPM4f3u5SyUSnlsiSrk5yb5Lokd9Va1w4eMhvf3zLamFPv1ceSURmdpebse/WxZFRGZ6k5+15d33zJZ9JtRi0uNCK1WT54Ti4hXErZIslZSd5ba717/fvm2n7XWh+qte6VZIc0P+HcfYZbYkjm2nt1fTLKXDDX3qvrk1Hmgrn2Xn3YfMpn0m1GRzF43pxkx/Vu7zCozQerSinbJcng6+oZ7mfoSimbpAnjabXWswflOb/ftda7kpyfZL8kW5VSHv6duLPx/S2jmbvvVRmV0Vluzr9XZVRGZ7k5/V6dr/lMusnoKAbPnyXZdbAa0uOSvC7JOSPYbh+ck+SYwfVjknxrBnsZulJKSXJykqtqrSeud9ec3O9SysJSylaD65slOSTN+f7nJzlq8LDZuL8y2piNf3cbJKMyOgfMyffqw2RURueAOfleTeZfPpMRZLTW2vklyWFJrklzjvBHR7HNUV+SnJFkZZIH05z7/NYkT06z2tWKJN9PsvVM9znkff69NKcX/DLJZYPLYXN1v5M8P8kvBvu7PMnHB/VnJLkkybVJvpFk05nudQr7JqNz6L263j7LaJXR2XKRURmV0X5f5ltG51s+B/vcaUbL4MUAAACgExYXAgAAoFMGTwAAADpl8AQAAKBTBk8AAAA6ZfAEAACgUwZPAAAAOmXwBAAAoFMGTwAAADpl8AQAAKBTBk8AAAA6ZfAEAACgUwZPAAAAOmXwBAAAoFMGTwAAADpl8AQAAKBTBk8AAAA6ZfAEAACgUwZPAAAAOmXwBAAAoFMGTwAAADpl8AQAAKBTBk8AAAA6ZfAEAACgUwZPAAAAOmXwBAAAoFMGTwAAADpl8AQAAKBTG49yY9tss03deeedR7lJ6J3rr78+t912W5npPtrIKMgo9J2MQr+NldGRDp4777xzli1bNspNQu8sXrx4plsYk4yCjELfySj021gZdaotAAAAnTJ4AgAA0CmDJwAAAJ0a6Wc8AQBmg2UX3NNaX3zgFiPuBGBucMQTAACAThk8AQAA6JTBEwAAgE6NO3iWUh5fSrmklHJ5KeWKUsonB/Wnl1J+Wkq5tpTy9VLK47pvF3gsGYV+k1HoNxmF0ZjIEc/7kxxca90zyV5JXl5K2TfJZ5N8vtb6zCR3Jnlrd20CGyCj0G8yCv0mozAC465qW2utSR5e2m2TwaUmOTjJ6wf1U5N8IskXh9/i7PbQAw+01i8+6aTW+s9PO621fsvy5ZPa7nbPe15rfe/Xv761niT7LVnSWt/ocX7A12cyCv0mo7PTFz+5srV+8oG7jrgTuiajMBoT+oxnKWWjUsplSVYnOTfJdUnuqrWuHTzkpiTbj/HcJaWUZaWUZbfeeuswegYeQ0ah32QU+k1GoXsTGjxrrQ/VWvdKskOSfZLsPtEN1FpPqrUurrUuXrhw4RTbBDZERqHfZBT6TUahe5Na1bbWeleS85Psl2SrUsrDp+rukOTmIfcGTJKMQr/JKPSbjEJ3JrKq7cJSylaD65slOSTJVWlCedTgYcck+VZXTQJjk1HoNxmFfpNRGI1xFxdKsl2SU0spG6UZVP+21vrtUsqVSf6mlPLpJL9IcnKHfQJjk1HoNxmFfpNRGIGJrGr7yyR7t9R/k+YceJL8n5vbz744+fDDW+s3X3ZZl+3k+osvnlQ9SS45uf3f07d++9ut9c2327a1fsE/XdBa/9WqX4257TbP3/b5rfUDnn5Aa33jBRP5OcrcI6PQbzLab8suuGdSdeYeGYXRmNRnPAEAAGCyDJ4AAAB0yuAJAABApwyeAAAAdMrgCQAAQKfm5zKg07D2/vtb6zO1eu0wjdXrx950UGv9e/+u/XWuuX3FsFpqtds2u7XWT3nNKa31Fy96cZftADCLffGTK2e6BYB5wRFPAAAAOmXwBAAAoFMGTwAAADpl8AQAAKBTBk8AAAA6ZVXbSfrJSSe11mfT6rVjuXnH9vqZL2pfpfah2ztsZgOuvu3q1vrBpxzcWv/BsT8Y87WseAswPyy74J5J1QEYLkc8AQAA6JTBEwAAgE4ZPAEAAOiUwRMAAIBOGTwBAADolMETAACATvl1KpN06de+NtMtTMu6Dfyo4XuHt9cf2qibXobt/rX3t9aPPfvYMZ+z/D3LW+sbLxANgLnki59cOdMtAMxrjngCAADQKYMnAAAAnRQchPAAABmBSURBVBp38Cyl7FhKOb+UcmUp5YpSyvGD+idKKTeXUi4bXA7rvl3gsWQU+k1God9kFEZjIh9kW5vk/bXWn5dStkxyaSnl3MF9n6+1/nl37QETIKPQbzIK/SajMALjDp611pVJVg6urymlXJVk+64bAyZGRqHfZBT6TUZhNCa1dGcpZeckeyf5aZL9k7yrlPLmJMvS/KTozpbnLEmyJEkWLVo0zXZn3qorr5zpFqblxp3Gvu/OrUfXxyhdfdvVY973w3/6YWv9Jbu8pKt2OiWj0G8y2r1lF9wzqTqsT0ahOxNeXKiUskWSs5K8t9Z6d5IvJtklyV5pfkr0F23Pq7WeVGtdXGtdvHDhwiG0DLSRUeg3GYV+k1Ho1oQGz1LKJmmCeFqt9ewkqbWuqrU+VGtdl+TLSfbprk1gQ2QU+k1God9kFLo3kVVtS5KTk1xVaz1xvfp26z3s1UmWD789YDwyCv0mo9BvMgqjMZHPeO6f5E1JflVKuWxQ+0iSo0speyWpSa5PclwnHQLjkVHoNxmFfpNRGIGJrGp7UZLSctd3h98OMFkyCv0mo9BvMgqjMalVbZn9bnvKTHfQL5ffcnlrfbauagswX6xbu7a1vvS9Z7XWn5VfTer178quY2z3eWM+Z8HGvq0CGMuEV7UFAACAqTB4AgAA0CmDJwAAAJ0yeAIAANApgycAAACdsvzaJG27xx6t9X++5JIRdzI1tW2x8Hms1jrTLQCwATf+6Eet9b8/9tjW+rbXXNNeH1I//99zPz/mfa885ZTW+g4vfvGQtg4wezniCQAAQKcMngAAAHTK4AkAAECnDJ4AAAB0yuAJAABAp6xqO0kveMMbWuuzZVXbhatmuoN+2Wu7vWa6BQCS3HjRRa31r770pa31h+6/v8t2xnT71VePed9XDj64tf7mH/ygtW61W2A+ccQTAACAThk8AQAA6JTBEwAAgE4ZPAEAAOiUwRMAAIBOWdV2kvY77rjW+iWnnNJa/+3ll3fZzqTteMPY9211Z3v9rid108uoPGubZ4153wFPP2CEnQCwbu3a1vo5xx7bWp+p1WunYqxex9q3P16+vLW+YGPfngEz47t3ntNaP+xJr5z2azviCQAAQKcMngAAAHRq3MGzlLJjKeX8UsqVpZQrSinHD+pbl1LOLaWsGHyd5Sdkwuwko9BvMgr9JqMwGhM54rk2yftrrXsk2TfJO0speyT5UJLzaq27JjlvcBsYPRmFfpNR6DcZhREYd/Csta6stf58cH1NkquSbJ/kVUlOHTzs1CRHdNUkMDYZhX6TUeg3GYXRmNSyaaWUnZPsneSnSbatta4c3HVLkm3HeM6SJEuSZNGiRVPtszc23nTT1vr/853vtNb/xyte0VqfqdVuF6wb+743/HrX1vr/+L32pXDvf+iBYbQ0NJtu3P53c8qr21ccTpKNF8ytlQNlFPpNRpPrzz+/tX7HihUj7mR0br/66tb6DT/8YWv96S95SZftsAEyynzxu3X3tdb/339+b2t9pKvallK2SHJWkvfWWu9e/75aa01S255Xaz2p1rq41rp44cKF02oWGJuMQr/JKPSbjEK3JjR4llI2SRPE02qtZw/Kq0op2w3u3y7J6m5aBMYjo9BvMgr9JqPQvYmsaluSnJzkqlrrievddU6SYwbXj0nyreG3B4xHRqHfZBT6TUZhNCbyAbf9k7wpya9KKZcNah9J8pkkf1tKeWuSG5K8tpsWgXHIKPSbjEK/ySiMwLiDZ631oiRljLt9+h1mmIxCv8ko9JuMwmjMrSU9Z9ATt9++tX78T3/aWr/4S19qrf/89NNb67csXz6pfp763Oe21v/tG94w5nP2XbKktf7vV17SWj/2745trV97+7XjdDc9uz65ffXdU17Tvnrt/jvt32U7AEzC6l/+cqZb6I1VY6xwb1VbZlJtXUIpufji9lVQL7/8d5N6/T333Ky1vt9+T2itl7F+JMC0/NnNn2it33D/P3W2zQmvagsAAABTYfAEAACgUwZPAAAAOmXwBAAAoFMGTwAAADplVduObbzppq3133/PeyZVn0m/v/Pvt9avfM+VrfUL/umC1vqvVv1qUtt93rbPa60f9IyDWusbL/B2Bui7um7dTLfQH2MtHwoj8NvfPthaP/LIf26t/+Qn7avaDstYq9qeeeai1vrTnrZJl+3MGVf97orW+n+/5S9H3IkjngAAAHTM4AkAAECnDJ4AAAB0yuAJAABApwyeAAAAdMoyoEzZJhu1ryZ2yDMPmVQdgPlj2732mukWesOfBaMw1kLSRxxxQ2v9Zz/7XYfdjO3ii9tXzX31q9tX2b344l1a6wvm4WG1mrFXyP7ADe9qrT9Y21c17tI8/KsBAABglAyeAAAAdMrgCQAAQKcMngAAAHTK4AkAAECnDJ4AAAB0yq9TAQBGZucDD2ytb/3MZ7bW77j22g67GY0nP+tZrfWdDjhgxJ0wH51//j2t9Zn6tSmTdckl7b9m5YIL2vfr4IO36LKdXvrqraeMed8/3n3B6BoZhyOeAAAAdMrgCQAAQKfGHTxLKaeUUlaXUpavV/tEKeXmUsplg8th3bYJjEVGod9kFPpNRmE0JnLEc2mSl7fUP19r3Wtw+e5w2wImYWlkFPpsaWQU+mxpZBQ6N+7gWWu9MMkdI+gFmAIZhX6TUeg3GYXRmM6qtu8qpbw5ybIk76+13tn2oFLKkiRLkmTRokXT2BwwSTJKr/zq9uta68978i4j7qQ35mVGF2yySWv9353Svirj1w45pLX+0P33D62nYdlo001b62Pt24KN/XKBnpsTGf3FL/5lplvoxGWXte/XXF7V9s617T8f+cSNHx5xJ1Mz1cWFvphklyR7JVmZ5C/GemCt9aRa6+Ja6+KFCxdOcXPAJMko9JuMQr/JKAzZlAbPWuuqWutDtdZ1Sb6cZJ/htgVMh4xCv8ko9JuMwvBNafAspWy33s1XJ1k+1mOB0ZNR6DcZhX6TURi+cT9cUEo5I8mBSbYppdyU5IQkB5ZS9kpSk1yf5LgOewQ2QEah32QU+k1GYTTGHTxrrUe3lE/uoBdgCmQU+k1God9kFEbDcmoAjMRpv/7frfXP7P/OEXdCHy36/d9vrb/x3HNb639/7LGt9TuuvXZoPbXZetddx7zvlWOsXrvj/vt31Q6Mq9Y60y10Yo7u1gZ97MYPttZvW3vriDuZmqmuagsAAAATYvAEAACgUwZPAAAAOmXwBAAAoFMGTwAAADplVVsAhmpdXdda/8aK81rrVrVlQ8Za7fbtV17ZWr/+ggta66t/9atJbfcpz3tea33ngw4a8zkLNvZtFf3zghdsNtMtdGLvvR8/0y105uI1F7XWv3br/xxxJ8PliCcAAACdMngCAADQKYMnAAAAnTJ4AgAA0CmDJwAAAJ2y/BoAQ/WDm5a11n97762db/uBhx5orb/779/dWv/SEV/qsh06tGCTTVrrzzjkkEnVYa476KAtWusvfGH7arc/+9nvumxn0vbZ5wmt9QMPbN+v2WRtXdta/8AN72qt19Qu2+mcI54AAAB0yuAJAABApwyeAAAAdMrgCQAAQKcMngAAAHTKqrYADNXpV/9D59u4dYwVcl97xmtb6xf80wWtdavaAnPdgjEOM33zmzu11o888p9b6z/5yX3DaqnVfvu1r1575pmLWutj7dds8le3nNha/9V9l4+4k9GYA39lAAAA9JnBEwAAgE6NO3iWUk4ppawupSxfr7Z1KeXcUsqKwdcnddsmMBYZhX6TUeg3GYXRmMgRz6VJXv6Y2oeSnFdr3TXJeYPbwMxYGhmFPlsaGYU+WxoZhc6NO3jWWi9Mcsdjyq9Kcurg+qlJjhhyX8AEySj0m4xCv8kojMZUV7Xdtta6cnD9liTbjvXAUsqSJEuSZNGi9lWpgKGTUTp374P/0lr/+9/849C2cenNl7bWX3P6a1rr/3xX+2qMPSSj0G9zPqNPe9omrfUf/3iX1vrFF7evanv55b+b1Hb33HOz1vpYq9qWMqmX76WbHmj/v+mzv/3UiDuZWdNeXKjWWpPUDdx/Uq11ca118cKFC6e7OWCSZBT6TUah32QUhmOqg+eqUsp2STL4unp4LQFDIKPQbzIK/SajMGRTHTzPSXLM4PoxSb41nHaAIZFR6DcZhX6TURiyifw6lTOSXJxkt1LKTaWUtyb5TJJDSikrkrx0cBuYATIK/Saj0G8yCqMx7uJCtdajx7jrJUPuBZgCGYV+k1HoNxmF0ZjqqrYAzHPf+s0PW+v3PDi5FQ6/8ouvjHnfcd88rrX+L2vbV9QFYHrGWkX2xS9uX3V2rDqP+MAN726t3/vQPSPuZGZNe1VbAAAA2BCDJwAAAJ0yeAIAANApgycAAACdMngCAADQKavaAjAlp1/9D0N5nTfv/eYp3QcAs8Hf7PqtmW6hFxzxBAAAoFMGTwAAADpl8AQAAKBTBk8AAAA6ZfAEAACgU1a1BWCDVt57W2v9gpsuHXEnAMBs5YgnAAAAnTJ4AgAA0CmDJwAAAJ0yeAIAANApgycAAACdsqotABv09WvOba0/VNeNuBMAYLZyxBMAAIBOGTwBAADo1LROtS2lXJ9kTZKHkqyttS4eRlPAcMgo9JuMQr/JKAzPMD7jeVCt9bYhvA7QDRmFfpNR6DcZhSFwqi0AAACdmu7gWZN8r5RyaSllSdsDSilLSinLSinLbr311mluDpgkGYV+k1HoNxmFIZnuqba/V2u9uZTylCTnllJ+XWu9cP0H1FpPSnJSkixevLhOc3vA5Mgo03b61f8w0y3MZTIK/SajMCTTOuJZa7158HV1kr9Lss8wmgKGQ0ah32QU+k1GYXimPHiWUjYvpWz58PUkL0uyfFiNAdMjo9BvMgr9JqMwXNM51XbbJH9XSnn4dU6vtf7voXQFDIOMQr/JKPSbjMIQTXnwrLX+JsmeQ+wFGCIZhX6TUeg3GYXh8utUAAAA6NR0V7UFYI675HVLZ7oFAGCWc8QTAACAThk8AQAA6JTBEwAAgE4ZPAEAAOiUwRMAAIBOGTwBAADolMETAACAThk8AQAA6JTBEwAAgE4ZPAEAAOiUwRMAAPrkm99Mrryym9c+8MBk2bJuXhs2wOAJAABdqjVZt27ij+9y8IQZYvAEAIBhu/76ZLfdkje/OXnuc5NPfSp54QuT5z8/OeGERx73la80tT33TN70puTHP07OOSf54AeTvfZKrrsu+fKXm+fuuWdy5JHJffc1z33LW5L3vCd58YuTZzwjOfPMpr5uXfKOdyS7754cckhy2GGP3Le+730v2W+/5AUvSP7oj5J77un6T4V5bOOZbgAAAOakFSuSU09N7r67GfwuuaQ5+vnKVyYXXpg8+cnJpz/dDJvbbJPccUey9dbN/Ycfnhx1VPM6W22VvO1tzfWPfSw5+eTk3e9ubq9cmVx0UfLrXzfPO+qo5Oyzm8H3yiuT1auTZz87OfbYR/d2223Ntr///WTzzZPPfjY58cTk4x8f2R8P84vBEwAAurDTTsm++yYf+EBzdHHvvZv6Pfc0Q+nllzdHGrfZpqlvvXX76yxf3gycd93VPPfQQx+574gjkgULkj32SFatamoXXdS87oIFyVOfmhx00L9+zZ/8pBlM99+/uf3AA83RT+iIwRMAALqw+ebN11qTD384Oe64R9//hS9M7HXe8pbmc5977pksXZpccMEj92266SPXa514b7U2p+GeccbEnwPT4DOeQDesyAcAjUMPTU455ZHPUN58c3MK7MEHJ9/4RnL77U39jjuar1tumaxZ88jz16xJttsuefDB5LTTxt/e/vsnZ53VfNZz1apHD6oP23ff5Ec/Sq69trl9773JNddMeRdhPAZPYGKsyAcAU/OylyWvf31zKuvzntd8DnPNmuQ5z0k++tHkgAOao5nve1/z+Ne9Lvnc55pTc6+7rlmY6EUvagbK3Xcff3tHHpnssENz+u0b39gsHvTEJz76MQsXNkdPjz66Wdxov/2az4lCR5xqC4zt+uubn9K+6EXJpZcmr31t8u1vJ/ffn7z61cknP9k87itfSf78z5NSmv+83v72ZkW+H/6wWbjgrLOSH/wgOemk5jMkz3xm8tWvJk94QnP60L/5N80RzFtuSf7Lf2n+Q163LnnXu5rn7bhjsskmzcIIDy+08LDvfa9ZHfD++5Nddkn+5/9Mtthi1H9SAPBoO+/cfDbzYccf31we65hjmsv69t//0T+8ffvbm8tjLV366NsPH1FdsKD5f3mLLZqjqfvs0wy8yaOPfh58cPKzn01wh2B6pnXEs5Ty8lLK1aWUa0spHxpWU8BwDCWjK1Y0S7J//vPNqUGXXJJcdlkziF54YXLFFc1w+YMfNIsk/Nf/2izr/spXNj+tveyyZiB8zWua/9wuv7xZXe/kkx/ZxsMr8n3728mHBm2uvyLfV7+aXHzxv+5t/RX5fv7zZPHiZkU+mCX8Pwr9Nqszevjhza9j+f3fT/7kT5pFhmAGTfmIZylloyR/neSQJDcl+Vkp5Zxaq3ProAeGllEr8kEn/D8K/TbrM9r2uU6YQdM51XafJNfWWn+TJKWUv0nyqiSzI4ww9w0no1bkg674fxT6TUZhiKZzqu32SW5c7/ZNgxrQD8PNqBX5YNj8Pwr9NumMXnrppSmlZOedd+6yL5iVOl9cqJSyJMmSwc37SynLN/T4OWibJLfNdBMjNN/2N5n8Pu/UVSNT0ZbR0047back2WLVqgUH3HnnE79z+ul3Jsnuu+yy2S677/74JFm76ab1x+94x91rtttu3S4HHLDp7s9//hPqggW5a9GitT9+5zvXPGWrrTbe56Mf3fKhE06oFx1//N3bHXbY4569xx5PuH/LLdfd/oxnrN1kxYry49NPX/PiFSu2vPmCC+6/4YEHHkiS165du83fnn76bVm3Li+6554tFj7taZv8buut1+WpT82Vl1xy38oHHnjwpStXPvEX3/3uvbc/85lrn/r612+y10tfuvmCtWtLkvzqqKPuvXGffR54wxvecMME/wjm23t2vu1vMgczOpP9jJj36/wwpzKaZPkNN9yQUsoMdjUy3q/zw1AyWupkTmtb/4ml7JfkE7XWQwe3P5wktdb/vIHnLKu1Lp7SBmep+bbP821/k/7u8zQz+qXRdDmO227bONtsszY33LBp9tnn1fnHf/xWnvWs303w2ceN/5D+/v11Zb7tb9Lfffb/6Pjm2/4m9rlPZHR8821/E/s8HdM54vmzJLuWUp6e5OYkr0vy+uk2BAzN7M/oAQf8Ye6553FZu3ajvP3tP5/E0AmzwezPKMxtMgpDNOXBs9a6tpTyriT/kGSjJKfUWq8YWmfAtEwzoxM6Wti5K67oRx/QAf+PQr/JKAzXtD7jWWv9bpLvTuIpJ01ne7PUfNvn+ba/SY/3WUYnZL7t83zb36TH+yyj45pv+5vY516R0XHNt/1N7POUTfkzngAAADAR0/l1KgAAADCukQyepZSXl1KuLqVcW0r50Ci2OWqllFNKKavXX+a+lLJ1KeXcUsqKwdcnzWSPw1ZK2bGUcn4p5cpSyhWllOMH9Tm536WUx5dSLimlXD7Y308O6k8vpfx08P7+einlcTPd62TJ6Nx6rz5MRmV0NpFRGZXRfptvGZ1v+Uy6z2jng2cpZaMkf53kD5PskeToUsoeXW93BixN8vLH1D6U5Lxa665JzhvcnkvWJnl/rXWPJPsmeefg73au7vf9SQ6ute6ZZK8kLy+l7Jvks0k+X2t9ZpI7k7x1BnucNBmdk+/Vh8mojM4mSyOjMiqjfbY08yuj8y2fSccZHcURz32SXFtr/U2t9YEkf5PkVSPY7kjVWi9Mcsdjyq9Kcurg+qlJjhhpUx2rta6stf58cH1NkquSbJ85ut+1cc/g5iaDS01ycJIzB/XZuL8y2piNf3cbJKMyOpvIqIwO6rNxf2W0MRv/7sY03/KZdJ/RUQye2ye5cb3bNw1q88G2tdaVg+u3JNl2JpvpUill5yR7J/lp5vB+l1I2KqVclmR1knOTXJfkrlrr2sFDZuP7W0Ybc+q9+lgyKqOz1Jx9rz6WjMroLDVn36vrmy/5TLrNqMWFRqQ2ywfPySWESylbJDkryXtrrXevf99c2+9a60O11r2S7JDmJ5y7z3BLDMlce6+uT0aZC+bae3V9MspcMNfeqw+bT/lMus3oKAbPm5PsuN7tHQa1+WBVKWW7JBl8XT3D/QxdKWWTNGE8rdZ69qA85/e71npXkvOT7Jdkq1LKw78Tdza+v2U0c/e9KqMyOsvN+feqjMroLDen36vzNZ9JNxkdxeD5syS7DlZDelyS1yU5ZwTb7YNzkhwzuH5Mkm/NYC9DV0opSU5OclWt9cT17pqT+11KWVhK2WpwfbMkh6Q53//8JEcNHjYb91dGG7Px726DZFRG54A5+V59mIzK6BwwJ9+ryfzLZzKCjNZaO78kOSzJNWnOEf7oKLY56kuSM5KsTPJgmnOf35rkyWlWu1qR5PtJtp7pPoe8z7+X5vSCXya5bHA5bK7ud5LnJ/nFYH+XJ/n4oP6MJJckuTbJN5JsOtO9TmHfZHQOvVfX22cZrTI6Wy4yKqMy2u/LfMvofMvnYJ87zWgZvBgAAAB0wuJCAAAAdMrgCQAAQKcMngAAAHTK4AkAAECnDJ4AAAB0yuAJAABApwyeAAAAdMrgCQAAQKf+f1L/TQQvGiIMAAAAAElFTkSuQmCC\n",
            "text/plain": [
              "<Figure size 1152x576 with 8 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    }
  ]
}