{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "33589708-97f1-3a68-fd67-6ddcb5559027"
      },
      "source": [
        "# Fix All The Errors\n",
        "\n",
        "This notebook has two goals... =)\n",
        "\n",
        "First, to get everyone on the same page with regards to the annotated data via crowd sourcing so that the communal kernels can move towards the fun stuff (deep learning!).\n",
        "\n",
        "The second is alert the contest authorities of potential discrepancies in the provided counts.csv file. Any such issues will need to be cleared both in the training data, but especially in the private data. I guess once the contest if over, we could simply spot-check if our algorithms correctly identified sea lions or not... but it would ruin the competition because there would have to be a recount at that point. In fact, its negative effect will be experienced prior to the contest close, as people will be training on noisy data. So let's begin!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "415b9040-e123-758d-9554-8c8777aabd9d"
      },
      "source": [
        "## Some Imports"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "951af0e8-e4c3-005b-a9e3-12edbff84981"
      },
      "outputs": [],
      "source": [
        "# Note: This code was intended to be ran by your home machine\n",
        "# and not the Kaggle kernel\n",
        "\n",
        "from multiprocessing import Pool\n",
        "from collections import Counter\n",
        "from time import time\n",
        "\n",
        "import numba, json, os, cv2, glob\n",
        "\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import skimage.feature"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "abd60e38-1e32-4bfb-0e13-b2af9be3ff33"
      },
      "source": [
        "## What We're Parsing"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "8b86f727-87d6-d55c-c5a0-ee5dcbbca720"
      },
      "outputs": [],
      "source": [
        "class_names = ['adult_females', 'adult_males', 'juveniles', 'pups', 'subadult_males']\n",
        "\n",
        "traincsv = pd.read_csv('../Train/train.csv', index_col=0)\n",
        "\n",
        "bad_train_ids = [str(i) + '.jpg' for i in [\n",
        "    # 7 no longer here. Manually rotate it 180* and your should be good\n",
        "    3, 9, 21, 30, 34, 71, 81, 89, 97, 151, 184, 215, 234, 242, \n",
        "    268, 290, 311, 331, 344, 380, 384, 406, 421, 469, 475, 490, 499, \n",
        "    507, 530, 531, 605, 607, 614, 621, 638, 644, 687, 712, 721, 767, \n",
        "    779, 781, 794, 800, 811, 839, 840, 869, 882, 901, 903, 905, 909, \n",
        "    913, 927, 946]]\n",
        "\n",
        "file_names = os.listdir(\"../TrainDotted/\")\n",
        "file_names = sorted(file_names, key=lambda \n",
        "                    item: (int(item.partition('.')[0]) if item[0].isdigit() else float('inf'), item)) \n",
        "\n",
        "#processed = glob.glob('../dots/*.txt')\n",
        "#if len(processed)>0: processed = [f[8:-4] for f in processed]\n",
        "\n",
        "# remove trash\n",
        "file_names = list(set(file_names) - set(bad_train_ids))# - set(processed))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "baac0a47-32ec-2a27-b37a-897f810281cd"
      },
      "source": [
        "Above is our first crowd-sourcing opportunity. I've already checked the first 10 images or so out of 57. Let's look through the rest and see:\n",
        "\n",
        " 1. Are they totally unique images, i.e.--different coordinates, etc. or---\n",
        " 2. Are they the same image just rotated?\n",
        "\n",
        "In the case of #2, make note of it so we can simply rotate the image as part of our automated pre-processing pipeline and use the image. I've manually rotated image #7 one-hundred and eighty degrees and it works fine with our kernel below."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "a5a81864-c75a-b7d8-5084-6f3c3283d11f"
      },
      "source": [
        "## Some Utility Methods"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "b4fba94b-3185-600c-a8ba-8b87c8ea0bf9"
      },
      "outputs": [],
      "source": [
        "@numba.jit\n",
        "def jit_blob(img):\n",
        "    # blob_log is the slowest executing of the three:\n",
        "    #  skimage.feature.blob_dog\n",
        "    #  skimage.feature.blob_doh\n",
        "    #  skimage.feature.blob_log\n",
        "    \n",
        "    # I've experimented with OpenCV Hough circles and that was even slower.\n",
        "    # Then again, that was before I added morphological erosion... hmm...\n",
        "    # Feel free to replace this with a cv2 circle detection method. If\n",
        "    # successfully implemented, it'll save us from weird annotations on\n",
        "    # our images like 'DEAD' seen on one of the images, ROFL.\n",
        "    return skimage.feature.blob_log(img, min_sigma=2.5, max_sigma=5, num_sigma=1, threshold=0.01, overlap=0.25)\n",
        "\n",
        "@numba.jit\n",
        "def meanpatch(img, x,y, mask=None):\n",
        "    # Here's my first contribution:\n",
        "    # Average a patch of pixels to get better accuracy\n",
        "    if mask is not None:\n",
        "        i = img[x-1:x+1, y-1:y+1].reshape(-1,3)\n",
        "        m = mask[x-1:x+1, y-1:y+1].reshape(-1,1)\n",
        "        m[m>0] = 1\n",
        "        return (m*i).mean(axis=0)\n",
        "    return img[x-1:x+1, y-1:y+1].reshape(-1,3).mean(axis=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "0927ee47-6741-711f-771d-c6689f6f1691"
      },
      "source": [
        "## The Main Method"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "2f0f4a8c-cc64-6ac5-ecd8-a7c6111614a8"
      },
      "source": [
        "This block of code has code borrowed from many other Kernels. As I get time later today / tomorrow, I'll properly reference each contributing author so no-one feels like their hard work got janked. I hope this will foster more crowd-sourcing so we can hurry up and clear this first step of the first stage."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "bc3f4bcb-31ac-d141-8084-e45171ccba58"
      },
      "outputs": [],
      "source": [
        "# This method takes in a single filename\n",
        "\n",
        "def coords(filename):\n",
        "    # Read the Train and Train Dotted images\n",
        "    # Looks like they're stored as BGR, which is weird to look at, so let's make it RGB\n",
        "    image_1 = cv2.cvtColor(cv2.imread(\"../TrainDotted/\" + filename), cv2.COLOR_BGR2RGB)\n",
        "    image_2 = cv2.cvtColor(cv2.imread(\"../Train/\" + filename), cv2.COLOR_BGR2RGB)\n",
        "\n",
        "    # For safe keepings...\n",
        "    width = image_1.shape[0]\n",
        "    height = image_1.shape[1]\n",
        "    if width != image_2.shape[0] or height != image_2.shape[1]:\n",
        "        with open('../dots/anomalies', 'a') as f:\n",
        "            f.write('width x height mismatch', filename)\n",
        "        return\n",
        "    \n",
        "    # Let's mask out the blackened regions that exist on *Either* image\n",
        "    mask_1 = cv2.cvtColor(image_1, cv2.COLOR_RGB2GRAY)\n",
        "    mask_1[mask_1 < 17] = 0\n",
        "    mask_1[mask_1 > 0] = 255\n",
        "\n",
        "    mask_2 = cv2.cvtColor(image_2, cv2.COLOR_RGB2GRAY)\n",
        "    mask_2[mask_2 < 17] = 0\n",
        "    mask_2[mask_2 > 0] = 255\n",
        "\n",
        "    # Absolute difference between Train and Train Dotted\n",
        "    img_diff = cv2.absdiff(image_1, image_2)\n",
        "    img_diff = cv2.bitwise_or(img_diff, img_diff, mask=mask_1 & mask_2)\n",
        "\n",
        "    # Convert to grayscale to be accepted by skimage.feature.blob_log\n",
        "    img_diff = cv2.cvtColor(img_diff, cv2.COLOR_RGB2GRAY)\n",
        "    \n",
        "    # Second contribution:\n",
        "    # JPEG compression causes some artification, so get rid of very small\n",
        "    # discrepancies between source images.\n",
        "    img_diff[img_diff < 5] = 0\n",
        "\n",
        "    # Third contribution - we have a lot of faint and bright dots\n",
        "    # left over to classify. Faint dots are at risk of being erased if we\n",
        "    # increase the #7 value above, or if use morphological erosion since\n",
        "    # that will make it harder to detect them. So instead, let's split the\n",
        "    # image into two parts, bright and dark regions... run erosion on the\n",
        "    # light portions only, then merge the results back together?\n",
        "    kernel = np.ones((3,3),np.uint8)\n",
        "    img_diff = cv2.morphologyEx(img_diff, cv2.MORPH_OPEN, kernel)\n",
        "\n",
        "    # Bilateral Filtering\n",
        "    # I've tried this by itself (e.g. without the MORPH_OPENING) and it\n",
        "    # was a disaster. Maybe try it w/ the opening? Anyhow, looking at the\n",
        "    # reported errors, I'm not seeing much where the script is wrong so...\n",
        "    # maybe it's not needed...?\n",
        "    #filter_dist = 7\n",
        "    #img_diff = cv2.bilateralFilter(img_diff.astype(np.float32), d=filter_dist, sigmaColor=filter_dist*2, sigmaSpace=int(filter_dist/2))\n",
        "    \n",
        "    #cv2.imwrite(\"diff.png\", img_diff) # For inspection ;-)\n",
        "    \n",
        "    # Detect bad data. If train and dotted images are very different then somethings wrong.\n",
        "    avg_diff = img_diff.sum() / (img_diff.shape[0] * img_diff.shape[1])\n",
        "    if avg_diff > 10:\n",
        "        #print(avg_diff, 'diff. Skipping bad data:', filename)\n",
        "        #skipped.append(filename)\n",
        "        with open('../dots/anomalies', 'a') as f: f.write(avg_diff, filename)\n",
        "        return # We're dipping since this will be multi-threaded\n",
        "\n",
        "    # detect blobs\n",
        "    blobs = jit_blob(img_diff)\n",
        "\n",
        "    # Init buckets\n",
        "    adult_males = []\n",
        "    subadult_males = []\n",
        "    pups = []\n",
        "    juveniles = []\n",
        "    adult_females = []\n",
        "    \n",
        "    # These centroids come from https://www.kaggle.com/threeplusone\n",
        "    dist = 32**2\n",
        "    cls_colors = np.array((\n",
        "        (243,8,5),          # red\n",
        "        (244,8,242),        # magenta\n",
        "        (87,46,10),         # brown \n",
        "        (25,56,176),        # blue\n",
        "        (38,174,21),        # green\n",
        "    ), dtype=np.int16)\n",
        "\n",
        "    for y, x, _ in blobs:\n",
        "        # The documentation for skimage says we get back y,x,sigma...\n",
        "        # but it seems like we're actually getting back x,y,sigma.\n",
        "        # Anyhow, don't let this bother you, it works fine... trust me.\n",
        "        \n",
        "        # Get the coordinates for each blob, and give some padding for \n",
        "        # our mean function:\n",
        "        y = int(max(1,y))\n",
        "        x = int(max(1,x))\n",
        "        y = min(y,width-2)\n",
        "        x = min(x,height-2)\n",
        "            \n",
        "        # Get the avg color of the pixelpatch from Train Dotted in the center of the blob\n",
        "        red = np.sum((cls_colors[0] - mp)**2) < dist\n",
        "        magenta = np.sum((cls_colors[1] - mp)**2) < dist\n",
        "        brown = np.sum((cls_colors[2] - mp)**2) < dist\n",
        "        blue = np.sum((cls_colors[3] - mp)**2) < dist\n",
        "        green = np.sum((cls_colors[4] - mp)**2) < dist\n",
        "\n",
        "        # Decision tree to pick the class of the blob by looking at the color in Train Dotted\n",
        "        if red: # r>204 and b<29 and g<26: # RED\n",
        "            adult_males.append((x,y))\n",
        "        elif magenta: # r>220 and b>204 and g<25: # MAGENTA\n",
        "            subadult_males.append((x,y))\n",
        "        elif green: # 6<r<64 and b<52 and 156<g<199: # GREEN\n",
        "            pups.append((x,y))\n",
        "        elif blue: #r<78 and  124<b<221 and 31<g<85: # BLUE\n",
        "            juveniles.append((x,y)) \n",
        "        elif brown: #59<r<115 and b<49 and 19<g<80:  # BROWN\n",
        "            adult_females.append((x,y))\n",
        "            #cv2.circle(image_circles, (int(x),int(y)), 20, (0,0,255), 2)\n",
        "        else:\n",
        "            #errors += 1\n",
        "            #cv2.circle(image_circles, (int(x),int(y)), 20, (255,255,255), 3)\n",
        "            pass\n",
        "        \n",
        "    obj = json.dumps({\n",
        "        'adult_males':   adult_males,\n",
        "        'subadult_males':subadult_males,\n",
        "        'adult_females': adult_females,\n",
        "        'juveniles':     juveniles,\n",
        "        'pups':          pups,\n",
        "        'counts':        [len(adult_males), len(subadult_males), len(adult_females), len(juveniles), len(pups)]\n",
        "    }, separators=(',',':'))\n",
        "\n",
        "    #print(len(adult_males), len(subadult_males), len(adult_females), len(juveniles), len(pups))\n",
        "    with open('../dots/' + filename[:-4] + '.txt', 'w') as f:\n",
        "        f.write(obj)\n",
        "    \n",
        "    #cv2.imwrite(\"error.png\", cv2.cvtColor(image_circles, cv2.COLOR_RGB2BGR))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "58198556-e28f-d6c3-5c0c-e569d2bed1e9"
      },
      "source": [
        "## Rock & Roll"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "1cf4caa6-bf65-0842-da5f-e9971d8d9676"
      },
      "outputs": [],
      "source": [
        "# I have a quad-core desktop, so dedicate 6 cores\n",
        "# That way I can browse the net w/o things getting choppy..\n",
        "# This process takes ~10 min on my Intel\u00ae Core\u2122 i7-4770 CPU @ 3.40GHz \u00d7 8, 64bit:\n",
        "thread_pool = Pool(6)\n",
        "thread_pool.map(coords, file_names)\n",
        "thread_pool.close()\n",
        "thread_pool.join()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "74f15c24-cc4e-56c5-26ef-16ec2d0a7e54"
      },
      "source": [
        "## Oops, 2 More Convenience Methods"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "0ffe8529-9b60-aa92-874d-533658455c1e"
      },
      "outputs": [],
      "source": [
        "def merge():\n",
        "    # The threads don't share memory, so this process will load up all the \n",
        "    # separately written .csv outputs and create a unified dataset called train_cs.csv\n",
        "    output = 'jpg,adult_males,subadult_males,adult_females,juveniles,pups\\n'\n",
        "    for filename in file_names:\n",
        "        sfname = filename[:-4]\n",
        "        with open('../dots/' + sfname + '.txt', 'r') as f:\n",
        "            data = json.loads(f.read())\n",
        "            output += \"%s,%i,%i,%i,%i,%i\\n\" %(sfname,data['counts'][0],data['counts'][1],data['counts'][2],data['counts'][3],data['counts'][4])\n",
        "\n",
        "    with open('../dots/train_cv.csv', 'w') as f:\n",
        "        f.write(output)\n",
        "\n",
        "\n",
        "def diff():\n",
        "    # This guy will produce the final output which tells us how\n",
        "    # well or bad we did compared to the provide dtrain.csv file.\n",
        "    cv = pd.read_csv('../dots/train_cv.csv', index_col=0)\n",
        "\n",
        "    results = []\n",
        "    for index, row in cv.iterrows():\n",
        "        fact = traincsv.loc[index].values\n",
        "        test = np.array([row['adult_males'], row['subadult_males'], row['adult_females'], row['juveniles'], row['pups']])\n",
        "        diff = fact-test\n",
        "        if np.abs(diff).sum() > 0:\n",
        "            results.append([index, fact-test, np.abs(fact-test).sum()])\n",
        "\n",
        "    results = pd.DataFrame(results, columns=['index', 'diff', 'absdiff'])\n",
        "    results.index = results['index']\n",
        "    results.sort_index(inplace=True)\n",
        "    results.sort_values(by='absdiff', inplace=True)\n",
        "\n",
        "    print(results)\n",
        "    totalerr=0\n",
        "    with open('../dots/train_diff.csv', 'w') as f:\n",
        "        for index, row in results.iterrows():\n",
        "            f.write(str(row['absdiff']) + '\\t' + str(row['index']) + '\\t' + str(row['diff']) + '\\n')\n",
        "            totalerr+=int(row['absdiff'])\n",
        "\n",
        "    print('totalerr',totalerr)\n",
        "    \n",
        "# Alright, let's run them:\n",
        "merge()\n",
        "diff()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "d04d00a9-437b-3b44-6667-c1eeb8ffd867"
      },
      "source": [
        "## Results\n",
        "\n",
        "How does this kernel compare to others? It can mimic the provided train.csv file with only 1020 elementwise-distance (sea lion) discrepancies. Of those 1020, if we throw out the **no-brainer** incorrect data, such as red pen annotations, it is reduced down to 436! Not bad. Now, what we need to do is manually figure out what is going on with those 436 discrepant seals, spread out in about 264 contested images. I've started the process, but would like some help! Care to join in? Add your notes in the comments!\n",
        "\n",
        "\n",
        "The format in the list below is AbsDiffValue, ImageNum, [Diff]. The Diff list is created by\n",
        "subtracting train.csv - counted seals--so positive numbers mean we under counted, and negative\n",
        "numbers mean we over counted.\n",
        "\n",
        "https://docs.google.com/spreadsheets/d/1cmUVp-RRcEO-DPbtK2nR3oAzX-ONDyGZ-BThevEgNE0/edit?usp=sharing"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "610749e1-b298-32e2-7739-fb2c8a6901a3"
      },
      "source": [
        "## Anomalies\n",
        "\n",
        " - 857.jpg does not have annotations at all. Should be added to bad_train_ids.\n",
        " - 200: pups marked with red dot, should be green\n",
        " - 7: if rotated 180 degrees, the algorithmic count matches provided count.csv\n",
        " - 61: is that really a girl?\n",
        " - 66: overexposed\n",
        "\n",
        "Thanks for reading~"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "4742bf7d-18eb-4dfe-12cb-0060f09a37bd"
      },
      "source": [
        "## TODO\n",
        "\n",
        "Some changes for me to merge into the google sheet.\n",
        "\n",
        "- 1   479 [0 1 0 0 0] traincsv says 5 magentas, only 4 present!! manually checked\n",
        "- 1   299 [0 1 0 0 0] i could only find 9 of the 10 claimed magentas. Anyone else find waldo?\n",
        "- 1   698 [0 1 0 0 0] train.csv says theres a magenta but there clearly isn't any. \n",
        "- 1   706 [0 1 0 0 0] traincsv says 5 magentas, only 4 present!! manually checked\n",
        "- 1   707 [0 1 0 0 0] traincsv says 22 magentas. in reality, there are only 21. and 1 of those 21 is an incorrect magenta in the sea that isn't a sealion\n",
        "- 1   538 [0 1 0 0 0] traincsv says 3 magentas, but only 3 present!! manually checked\n",
        "- 1   492 [0 1 0 0 0] traincsv says 2 magentas, but there is clearly only 1\n",
        "- 1   604 [0 1 0 0 0] train.csv says 5 magentas, only 4!! manually checked\n",
        "- 4   398 [-1  3  0  0  0] improperly masked redlines. confirmed bad annotated traindotted.jpg, train.csv is correct\n",
        "- 1   727 [-1  0  0  0  0] improperly masked redlines.  confirmed bad annotated traindotted.jpg, train.csv is correct\n",
        "- 1   221 [-1  0  0  0  0] manually found 6 marked red dots; train.csv incorrectly says only 5. *****\n",
        "- 1   65  [-1  0  0  0  0] improperly masked redlines in top left corner.  confirmed bad annotated traindotted.jpg\n",
        "- 1   259 [-1  0  0  0  0] red dot in the sea. confirmed bad annotated traindotted.jpg, train.csv is correct\n",
        "- 1   448 [-1  0  0  0  0] improperly masked red dot at the center of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 1   275 [-1  0  0  0  0] red dot in the sea. confirmed bad annotated traindotted.jpg, train.csv is correct\n",
        "- 1   58  [-1  0  0  0  0] improperly masked red dot at the top right of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 2   174 [-1  0  1  0  0] improperly masked red dot at the bottom center of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 2   741 [-2  0  0  0  0] two red dots in the sea. confirmed bad annotated traindotted.jpg, train.csv is correct\n",
        "- 2   742 [-2  0  0  0  0] improperly masked red line at the center left of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 4   889 [-2  1  0  0  1] improperly masked red line at the center of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 4   526 [-2  1 -1  0  0] improperly masked red line at the bottom center of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 7   899 [-2  1 -3 -1  0] found two real, correctly dotted man-sealions.  train.csv says 0. confirmed bad train.csv count *****\n",
        "- 3   291 [-2  0 -1  0  0] improperly masked red line at the top left of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 3   452 [-3  0  0  0  0] improperly masked red lines at the center right, and bottom right of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 3   523 [-3  0  0  0  0] improperly masked red line at the center right of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 3   510 [-3  0  0  0  0] found five real, correctly dotted man-sealions. train.csv says only 3.  confirmed bad train.csv count *****\n",
        "- 3   126 [-3  0  0  0  0] improperly masked red line at the top right of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 5   776 [-5  0  0  0  0] found eight real, correctly dotted man-sealions. train.csv says only 3.  confirmed bad train.csv count *****\n",
        "- 55  66  [ -8  -5 -23 -17  -2] found eight real, correctly dotted man-sealions. train.csv says 0. +other issues  confirmed bad train.csv count *****\n",
        "- 60  292 [ -3  -2 -27 -28   0] found five real, correctly dotted man-sealions. train.csv says 2. +other issues.  confirmed bad train.csv count *****\n",
        "- 8   788 [-7  1  0  0  0] improperly masked red line at the bottom right of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 11  931 [-11   0   0   0   0] improperly masked red line at the bottom left of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 18  566 [-17   0  -1   0   0] improperly masked red line at the center left of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 23  759 [-20   3   0   0   0] DEAD written in red ink on a sea lion in the center.  confirmed bad annotated traindotted.jpg\n",
        "- 25  847 [-25   0   0   0   0] improperly masked red line at the top-center and bottom-center of the image.  confirmed bad annotated traindotted.jpg\n",
        "- 33  371 [-33   0   0   0   0] improperly masked red line at the top center.  confirmed bad annotated traindotted.jpg\n",
        "- 36  513 [-36   0   0   0   0] improperly masked red line at the right center.  confirmed bad annotated traindotted.jpg\n",
        "- 75  338 [-75   0   0   0   0] improperly masked red line at the bottom center.  confirmed bad annotated traindotted.jpg"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "4c3234a8-9598-b7c2-b55a-4e41298f6122"
      },
      "outputs": [],
      "source": [
        ""
      ]
    }
  ],
  "metadata": {
    "_change_revision": 0,
    "_is_fork": false,
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "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.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}