{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step B"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "from matplotlib import pyplot as plt\n",
    "import skimage.morphology as sk\n",
    "from operator import itemgetter\n",
    "import os"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```compute_all_keypoints``` function calculates all keypoints of all query and train images and stores them in a dictionary, in order to easily access them later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_all_keypoints(query_imgs, train_imgs, sift):\n",
    "\n",
    "    img_dict = {}\n",
    "\n",
    "    for img in query_imgs:\n",
    "        file = 'models/' + img + '.jpg'\n",
    "        query = cv2.imread(file, 0)\n",
    "        kp, des = sift.detectAndCompute(query, None)\n",
    "        img_dict[img] = {'kp': kp, 'des': des, 'shape': query.shape}\n",
    "\n",
    "    for img in train_imgs:\n",
    "        file = 'scenes/' + img + '.png'\n",
    "        train = cv2.imread(file, 0)\n",
    "        kp, des = sift.detectAndCompute(train, None)\n",
    "        img_dict[img] = {'kp': kp, 'des': des, 'shape': train.shape}\n",
    "\n",
    "    return img_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```apply_ratio_test``` function takes all the matches found between the query and the train image, it chooses the good ones with the usual ratio test and it stores them in a dictionary using the indexes of the query keypoints as keys and the indexes of the train keypoints as values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def apply_ratio_test(all_matches):\n",
    "\n",
    "    # map of matches kp_train_idx -> kp_query_idx\n",
    "    good_matches = {}\n",
    "\n",
    "    for m, n in all_matches:\n",
    "        if m.distance < LOWE_COEFF * n.distance:\n",
    "            good_matches[m.queryIdx] = m.trainIdx\n",
    "      \n",
    "    return good_matches"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```compute_entry_hough_space``` function maps a point into the 4-dimensional Hough space, the ```create_hough_space``` function computes the Hough space entries for all keypoints."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_entry_hough_space(kp_q, kp_t, q_xc, q_yc):\n",
    "\n",
    "    entry = {}\n",
    "\n",
    "    v = ((q_xc - kp_q.pt[0]), (q_yc - kp_q.pt[1]))\n",
    "    scale_ratio = kp_t.size / kp_q.size\n",
    "    delta_angle = kp_t.angle - kp_q.angle\n",
    "    x_c = kp_t.pt[0] + scale_ratio * (np.cos(delta_angle) * v[0] - np.sin(delta_angle) * v[1])\n",
    "    y_c = kp_t.pt[1] + scale_ratio * (np.sin(delta_angle) * v[0] + np.cos(delta_angle) * v[1])\n",
    "\n",
    "    entry['x_c'] = x_c\n",
    "    entry['y_c'] = y_c\n",
    "    entry['scale_ratio'] = scale_ratio\n",
    "    entry['delta_angle'] = delta_angle\n",
    "    \n",
    "    return entry "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_hough_space(good_matches, kp_query, kp_train, query_xc, query_yc):\n",
    "    \n",
    "    # map of hough space kp_train_idx -> map name-values\n",
    "    hough_space = {}\n",
    "\n",
    "    for t_idx, q_idx in good_matches.items():\n",
    "        hough_space[t_idx] = compute_entry_hough_space(kp_query[q_idx], kp_train[t_idx], query_xc, query_yc)\n",
    "    \n",
    "    return hough_space"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```compute_bins``` function partitions the 4-dimensional Hough space into discrete bins."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_bins(hough_space,query_shape,train_shape):\n",
    "\n",
    "    values = {}\n",
    "    \n",
    "    data_scale = [entry['scale_ratio'] for entry in hough_space.values()]\n",
    "    counts_scale, bins_scale, patches_size = plt.hist(data_scale, bins='auto')\n",
    "    img_scale = np.mean([bins_scale[np.argmax(counts_scale)], bins_scale[np.argmax(counts_scale) + 1]])\n",
    "    plt.close();\n",
    "\n",
    "    data_angle = [entry['delta_angle'] for entry in hough_space.values()]\n",
    "    counts_angle, bins_angle, patches_angle = plt.hist(data_angle, bins='auto')\n",
    "    plt.close();\n",
    "\n",
    "    x_bin_size = img_scale * query_shape[1] * BIN_PRECISION_FACTOR\n",
    "    y_bin_size = img_scale * query_shape[0] * BIN_PRECISION_FACTOR\n",
    "    x_bins = int(np.ceil(train_shape[1] / x_bin_size) + 2)\n",
    "    y_bins = int(np.ceil(train_shape[0] / y_bin_size) + 2)\n",
    "    x_min = train_shape[1] / 2 - x_bins / 2 * x_bin_size\n",
    "    y_min = train_shape[0] / 2 - y_bins / 2 * y_bin_size\n",
    "\n",
    "    angle_bin_size = np.std(data_angle) * ANGLE_BIN_SIZE_COEFF\n",
    "    angle_bin_center = np.mean(data_angle)\n",
    "    angle_min = angle_bin_center - ANGLE_BINS / 2 * angle_bin_size\n",
    "    angle_max = angle_bin_center + ANGLE_BINS / 2 * angle_bin_size\n",
    "\n",
    "    scale_bin_size = np.std(data_scale) * SCALE_BIN_SIZE_COEFF\n",
    "    scale_bin_center = np.mean(data_scale)\n",
    "    scale_min = 0 \n",
    "    scale_max = scale_bin_center * 2 \n",
    "    scale_bins = int((scale_max - scale_min) / scale_bin_size)\n",
    "\n",
    "    values['x_bins'] = x_bins\n",
    "    values['y_bins'] = y_bins\n",
    "    values['x_min'] = x_min\n",
    "    values['y_min'] = y_min\n",
    "    values['x_bin_size'] = x_bin_size\n",
    "    values['y_bin_size'] = y_bin_size\n",
    "    values['scale_bins'] = scale_bins\n",
    "    values['scale_min'] = scale_min\n",
    "    values['scale_bin_size'] = scale_bin_size\n",
    "    values['angle_min'] = angle_min\n",
    "    values['angle_bin_size'] = angle_bin_size  \n",
    "\n",
    "    return values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```voting``` function attributes 16 bins to each point in the 4-dimensional Hough space."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def voting(b,h_s):\n",
    "\n",
    "    accumulator = np.zeros((b['x_bins'], b['y_bins'], ANGLE_BINS, b['scale_bins']))\n",
    "\n",
    "    votes = {}\n",
    "\n",
    "    for idx, v in h_s.items():\n",
    "        try:\n",
    "            for x in range(0, 2):\n",
    "                for y in range(0, 2):\n",
    "                    for z in range(0, 2):\n",
    "                        for w in range(0, 2):                \n",
    "                            i = int(np.floor((v['x_c'] - b['x_min'] + (x - 1 / 2) * b['x_bin_size']) / b['x_bin_size']))\n",
    "                            j = int(np.floor((v['y_c'] - b['y_min'] + (y - 1 / 2) * b['y_bin_size']) / b['y_bin_size']))\n",
    "                            k = int(np.floor((v['delta_angle'] - b['angle_min'] + (z - 1 / 2) * b['angle_bin_size']) / b['angle_bin_size']))\n",
    "                            l = int(np.floor((v['scale_ratio'] - b['scale_min'] + (w - 1 / 2) * b['scale_bin_size']) / b['scale_bin_size']))\n",
    "                            if i >= 0 and j >= 0 and k >= 0 and l >= 0:\n",
    "                                accumulator[i, j, k, l] += 1\n",
    "                                votes[(i, j, k, l)] = votes.get((i, j, k, l), [])\n",
    "                                votes[(i, j, k, l)].append(idx)\n",
    "        except: pass\n",
    "    \n",
    "    return accumulator, votes   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```find_all_correspondeces``` function computes all the correspondeces between query points and train points that voted for a local maxima."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_all_correspondeces(query_imgs, train_img, img_dict, bf):\n",
    "\n",
    "    # list of lists of all correspondent point of every image  \n",
    "    global_correspondences = []\n",
    "\n",
    "    for query_img in query_imgs:\n",
    "        \n",
    "        # compute keypoints and desctiptors for query and train\n",
    "        kp_query, des_query = img_dict[query_img]['kp'], img_dict[query_img]['des']\n",
    "        kp_train, des_train = img_dict[train_img]['kp'], img_dict[train_img]['des']\n",
    "\n",
    "        # match descriptors between the two images \n",
    "        all_matches = bf.knnMatch(des_train, des_query, k=2)\n",
    "\n",
    "        # create map of matching keypoint indexes surviving the lowe ratio test \n",
    "        good_matches = apply_ratio_test(all_matches)\n",
    "\n",
    "        # barycenter of found query keypoint \n",
    "        query_xc = np.mean(list(kp_query[p].pt[0] for _, p in good_matches.items()))\n",
    "        query_yc = np.mean(list(kp_query[p].pt[1] for _, p in good_matches.items()))\n",
    "\n",
    "        # create hough space \n",
    "        hough_space = create_hough_space(good_matches, kp_query, kp_train, query_xc, query_yc)\n",
    "\n",
    "        # do not go on with this query image if the number of entries in the hough space are below a certain threshold\n",
    "        if len(hough_space) < HOUGH_T: continue\n",
    "\n",
    "        # compute all the values related to the size \n",
    "        bins_values = compute_bins(hough_space, img_dict[query_img]['shape'], img_dict[train_img]['shape'])\n",
    "\n",
    "        # create and populate accumulator with voting by each entry of the hough space \n",
    "        accumulator, votes= voting(bins_values, hough_space)\n",
    "\n",
    "        # compute local maxima of the 4-dimensional accumulator \n",
    "        mask = sk.local_maxima(accumulator)\n",
    "        accumulator[mask != 1] = 0\n",
    "\n",
    "        # store in a list all the correspondeces between query points and train points that voted for a local maxima \n",
    "        # the list contains: number of votes that a local maxima bin has received, name of query image, list of query and train keypoints which voted for that bin\n",
    "        for b in list(np.argwhere(accumulator >= T_Q)): # thresholding the accumulator to come up with few maxima \n",
    "            keypoint_index_list = votes[tuple(b)] # all query keypoint who voted for a local maxima bin \n",
    "            correspondence_list = [(kp_train[k], kp_query[good_matches[k]]) for k in keypoint_index_list]\n",
    "            global_correspondences.append([accumulator[tuple(b)], query_img, correspondence_list])\n",
    "\n",
    "    # sorted correspondences based on number of votes found in local maxima bins \n",
    "    g_c = sorted(global_correspondences, key=itemgetter(0), reverse=True)\n",
    "\n",
    "    return g_c "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```check_matches``` function orders the good matches in decreasing number of keypoints and it runs a series of tests on them, checking the geometric arrangement and the color consistency."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def check_matches(correspondences, train_img):\n",
    "\n",
    "    train_file = 'scenes/' + train_img + '.png'\n",
    "    train_bgr = cv2.imread(train_file)\n",
    "\n",
    "    #dict query_name -> list of projected query vertex into train image\n",
    "    recognised = {}\n",
    "\n",
    "    for entry in correspondences:\n",
    "        try:\n",
    "\n",
    "            query_file = 'models/' + entry[1] + '.jpg'\n",
    "            query_bgr = cv2.imread(query_file)\n",
    "            \n",
    "            # compute homography through correspondent keypoints\n",
    "            src_pts = np.float32([e[1].pt for e in entry[2]]).reshape(-1, 1, 2)\n",
    "            dst_pts = np.float32([e[0].pt for e in entry[2]]).reshape(-1, 1, 2)\n",
    "            M, _ = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)\n",
    "            h, w, d = query_bgr.shape\n",
    "            pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1,1,2)\n",
    "            dst = cv2.perspectiveTransform(pts, M)\n",
    "\n",
    "            # determine center of the train ROI matching with the image query\n",
    "            center = tuple((dst[0, 0, i] + dst[1, 0, i] + dst[2, 0, i] + dst[3, 0, i]) / 4 for i in (0, 1))\n",
    "\n",
    "            # determine extreme points of the quadrilateral shape of query image projected into train scene\n",
    "            x_min = int(max((dst[0,0,0] + dst[1,0,0]) / 2, 0))\n",
    "            y_min = int(max((dst[0,0,1] + dst[3,0,1]) / 2, 0))\n",
    "            x_max = int(min((dst[2,0,0] + dst[3,0,0]) / 2, train_bgr.shape[1]))\n",
    "            y_max = int(min((dst[1,0,1] + dst[2,0,1]) / 2, train_bgr.shape[0]))\n",
    "\n",
    "            # compute main color of both query and train ROI to tell similar boxes apart\n",
    "            query_color = query_bgr.mean(axis=0).mean(axis=0)\n",
    "            train_crop = train_bgr[y_min:y_max, x_min:x_max]\n",
    "            train_color = train_crop.mean(axis=0).mean(axis=0)   \n",
    "            color_diff = np.sqrt(np.sum([value ** 2 for value in abs(query_color - train_color)]))\n",
    "            \n",
    "            # recognise a query in the scene only if its ROI color is similar to the query color and it does not overlap with another already placed box\n",
    "            temp = True\n",
    "            if color_diff < COLOR_T:\n",
    "                for k, v in recognised.items():\n",
    "                    for corners in v:\n",
    "                        r_center = tuple((corners[0, 0, i] + corners[1, 0, i] + corners[2, 0, i] + corners[3, 0, i]) / 4 for i in (0, 1))\n",
    "                        if (center[0] > min(corners[0, 0, 0], corners[1, 0, 0]) and center[0] < max(corners[2, 0, 0], corners[3, 0, 0])\\\n",
    "                            and center[1] > min(corners[0, 0, 1], corners[3, 0, 1]) and center[1] < max(corners[1, 0, 1], corners[2, 0, 1]))\\\n",
    "                            or (r_center[0] > x_min and r_center[0] < x_max\\\n",
    "                            and r_center[1] > y_min and r_center[1] < y_max):\n",
    "                            temp = False\n",
    "                            break\n",
    "                if temp:\n",
    "                    recognised[entry[1]] = recognised.get(entry[1], [])  \n",
    "                    recognised[entry[1]].append(dst)\n",
    "        except: pass\n",
    "    \n",
    "    return recognised"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```print_matches``` function takes all the recognised images and prints their details, i.e. their position, width, and height."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_matches(train_img, query_imgs, recognised, true_imgs, verbose):\n",
    "    \n",
    "    print('Scene: ' + train_img + '\\n')\n",
    "\n",
    "    for query_img in query_imgs:\n",
    "                     \n",
    "        total = len(recognised.get(query_img, []))\n",
    "        true_total = true_imgs[train_img][query_img]\n",
    "\n",
    "        if total != true_total:\n",
    "            print('\\033[1m' + 'Product ' + query_img + ' – ' + str(total) + '/' + str(true_total) + ' instances found' + '\\033[0m')\n",
    "        elif total > 0 or verbose == True:\n",
    "            print('Product ' + query_img + ' – ' + str(total) + '/' + str(true_total) + ' instances found')\n",
    "            \n",
    "        for j in range(total):\n",
    "            dst = recognised[query_img][j]\n",
    "            center = tuple(int((dst[0, 0, i] + dst[1, 0, i] + dst[2, 0, i] + dst[3, 0, i]) / 4) for i in (0, 1))\n",
    "            w = int(((dst[3, 0, 0] - dst[0, 0, 0]) + (dst[2, 0, 0] - dst[1, 0, 0])) /2)\n",
    "            h = int(((dst[1, 0, 1] - dst[0, 0, 1]) + (dst[2, 0, 1] - dst[3, 0, 1])) /2)\n",
    "            print('\\t' + 'Position: ' + str(center)\\\n",
    "                 + '\\t' + 'Width: ' + str(w)\\\n",
    "                 + '\\t' + 'Height: ' + str(h))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```draw_matches``` function draws on the train image the boxes' homographies and the numbers corresponding to the query images."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def draw_matches(recognised, train_img, color):\n",
    "\n",
    "    train_file = 'scenes/' + train_img + '.png'\n",
    "\n",
    "    # if color option is enabled all the results are printed on colored images \n",
    "    if color == True:\n",
    "        train_bgr = cv2.imread(train_file)\n",
    "        train_temp = cv2.cvtColor(train_bgr, cv2.COLOR_BGR2RGB)\n",
    "        train_rgb = np.zeros(train_bgr.shape, train_bgr.dtype)\n",
    "        for y in range(train_temp.shape[0]):\n",
    "            for x in range(train_temp.shape[1]):\n",
    "                for c in range(train_temp.shape[2]):\n",
    "                    train_rgb[y, x, c] = np.clip(0.5 * train_temp[y, x, c], 0, 255)\n",
    "    else:\n",
    "        train_gray = cv2.imread(train_file, 0)\n",
    "        train_rgb = cv2.cvtColor(train_gray // 2, cv2.COLOR_GRAY2RGB)\n",
    "\n",
    "    # for each recognised box in the scene draw the bounding box with its number in it \n",
    "    for k, v in recognised.items():\n",
    "        for dst in v:\n",
    "            \n",
    "            train_rgb = cv2.polylines(train_rgb, [np.int32(dst)], True, (0, 255, 0), 3, cv2.LINE_AA)\n",
    "            font = cv2.FONT_HERSHEY_SIMPLEX\n",
    "            cv2.putText(train_rgb, k,\\\n",
    "                        (int((dst[3, 0, 0] - dst[0, 0, 0]) * 0.25 + dst[0, 0, 0]), int((dst[1, 0, 1] - dst[0, 0, 1]) * 0.67 + dst[0, 0, 1])),\\\n",
    "                        font, 5, (0, 255, 0), 10, cv2.LINE_AA)        \n",
    "           \n",
    "    plt.imshow(train_rgb),plt.show();\n",
    "\n",
    "    if color == True:\n",
    "        \n",
    "        if not os.path.exists('output/step_B/'):\n",
    "            os.mkdir('output/step_B/')\n",
    "\n",
    "        cv2.imwrite('output/step_B/' + train_img + '.png', cv2.cvtColor(train_rgb, cv2.COLOR_RGB2BGR))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```step_B``` function takes the lists of query and train images and performs the product recognition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    " def step_B(query_imgs, train_imgs, true_imgs, verbose, color):\n",
    "\n",
    "    sift = cv2.xfeatures2d.SIFT_create()\n",
    "\n",
    "    # descriptor matcher \n",
    "    bf = cv2.BFMatcher()\n",
    "\n",
    "    img_dict = compute_all_keypoints(query_imgs, train_imgs, sift)    #compute all keypoints for all images once for all \n",
    "\n",
    "    for train_img in train_imgs:\n",
    "\n",
    "        g_c = find_all_correspondeces(query_imgs, train_img, img_dict, bf)\n",
    "\n",
    "        recognised = check_matches(g_c, train_img)\n",
    "        \n",
    "        print_matches(train_img, query_imgs, recognised, true_imgs, verbose)\n",
    "            \n",
    "        draw_matches(recognised, train_img, color)\n",
    "\n",
    "        print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parameters:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "LOWE_COEFF = 0.8\n",
    "BIN_PRECISION_FACTOR = 0.25\n",
    "ANGLE_BINS = 7\n",
    "ANGLE_BIN_SIZE_COEFF = 0.1\n",
    "SCALE_BIN_SIZE_COEFF = 0.1\n",
    "T_Q = 5\n",
    "COLOR_T = 50\n",
    "HOUGH_T = 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "query_imgs = ['0', '1', '11', '19', '24', '25', '26']\n",
    "train_imgs = ['e1', 'e2', 'e3', 'e4', 'e5',\\\n",
    "              'm1', 'm2', 'm3', 'm4', 'm5']\n",
    "true_imgs = {\n",
    "    'e1': {'0': 1, '1': 0, '11': 1, '19': 0, '24': 0, '25': 0, '26': 0},\n",
    "    'e2': {'0': 0, '1': 0, '11': 0, '19': 0, '24': 1, '25': 1, '26': 1},\n",
    "    'e3': {'0': 1, '1': 1, '11': 1, '19': 0, '24': 0, '25': 0, '26': 0},\n",
    "    'e4': {'0': 1, '1': 0, '11': 1, '19': 0, '24': 0, '25': 1, '26': 1},\n",
    "    'e5': {'0': 0, '1': 0, '11': 0, '19': 1, '24': 0, '25': 1, '26': 0},\n",
    "    'm1': {'0': 0, '1': 0, '11': 0, '19': 0, '24': 2, '25': 1, '26': 1},\n",
    "    'm2': {'0': 1, '1': 2, '11': 1, '19': 0, '24': 0, '25': 0, '26': 0},\n",
    "    'm3': {'0': 0, '1': 0, '11': 0, '19': 1, '24': 0, '25': 2, '26': 1},\n",
    "    'm4': {'0': 0, '1': 0, '11': 0, '19': 0, '24': 2, '25': 2, '26': 1},\n",
    "    'm5': {'0': 0, '1': 2, '11': 1, '19': 1, '24': 0, '25': 2, '26': 0},\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# verbose=False does not print the true negative instances\n",
    "# color=True outputs all the scenes in color instead of grayscale and saves them, but the process is quite slow\n",
    "step_B(query_imgs, train_imgs, true_imgs, verbose=False, color=False)"
   ]
  }
 ],
 "metadata": {
  "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.5.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
