{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import cv2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f2ba16e8940>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 74756 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "image = cv2.cvtColor(cv2.imread(\"semarak-jawi.2.jpg\"), cv2.COLOR_BGR2RGB)\n",
    "plt.imshow(image)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "SMALL_HEIGHT = 800\n",
    "\n",
    "def resize(img, height=SMALL_HEIGHT, allways=False):\n",
    "    \"\"\"Resize image to given height.\"\"\"\n",
    "    if (img.shape[0] > height or allways):\n",
    "        rat = height / img.shape[0]\n",
    "        return cv2.resize(img, (int(rat * img.shape[1]), height))\n",
    "    \n",
    "    return img\n",
    "\n",
    "def implt(img, cmp=None, t=''):\n",
    "    \"\"\"Show image using plt.\"\"\"\n",
    "    plt.imshow(img, cmap=cmp)\n",
    "    plt.title(t)\n",
    "    plt.show()\n",
    "    \n",
    "def ratio(img, height=SMALL_HEIGHT):\n",
    "    \"\"\"Getting scale ratio.\"\"\"\n",
    "    return img.shape[0] / height\n",
    "\n",
    "def edges_det(img, min_val, max_val):\n",
    "    \"\"\" Preprocessing (gray, thresh, filter, border) + Canny edge detection \"\"\"\n",
    "    img = cv2.cvtColor(resize(img), cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "    # Applying blur and threshold\n",
    "    img = cv2.bilateralFilter(img, 9, 75, 75)\n",
    "    img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 115, 4)\n",
    "    implt(img, 'gray', 'Adaptive Threshold')\n",
    "\n",
    "    # Median blur replace center pixel by median of pixels under kelner\n",
    "    # => removes thin details\n",
    "    img = cv2.medianBlur(img, 11)\n",
    "\n",
    "    # Add black border - detection of border touching pages\n",
    "    # Contour can't touch side of image\n",
    "    img = cv2.copyMakeBorder(img, 5, 5, 5, 5, cv2.BORDER_CONSTANT, value=[0, 0, 0])\n",
    "    implt(img, 'gray', 'Median Blur + Border')\n",
    "\n",
    "    return cv2.Canny(img, min_val, max_val)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18728 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 14052 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 13736 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "edges_image = edges_det(image, 200, 250)\n",
    "\n",
    "# Close gaps between edges (double page clouse => rectangle kernel)\n",
    "edges_image = cv2.morphologyEx(edges_image, cv2.MORPH_CLOSE, np.ones((5, 11)))\n",
    "implt(edges_image, 'gray', 'Edges')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def four_corners_sort(pts):\n",
    "    \"\"\" Sort corners: top-left, bot-left, bot-right, top-right\"\"\"\n",
    "    diff = np.diff(pts, axis=1)\n",
    "    summ = pts.sum(axis=1)\n",
    "    return np.array([pts[np.argmin(summ)],\n",
    "                     pts[np.argmax(diff)],\n",
    "                     pts[np.argmax(summ)],\n",
    "                     pts[np.argmin(diff)]])\n",
    "\n",
    "\n",
    "def contour_offset(cnt, offset):\n",
    "    \"\"\" Offset contour because of 5px border \"\"\"\n",
    "    cnt += offset\n",
    "    cnt[cnt < 0] = 0\n",
    "    return cnt\n",
    "\n",
    "\n",
    "def find_page_contours(edges, img):\n",
    "    \"\"\" Finding corner points of page contour \"\"\"\n",
    "    # Getting contours  \n",
    "    contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n",
    "    \n",
    "    # Finding biggest rectangle otherwise return original corners\n",
    "    height = edges.shape[0]\n",
    "    width = edges.shape[1]\n",
    "    MIN_COUNTOUR_AREA = height * width * 0.5\n",
    "    MAX_COUNTOUR_AREA = (width - 10) * (height - 10)\n",
    "\n",
    "    max_area = MIN_COUNTOUR_AREA\n",
    "    page_contour = np.array([[0, 0],\n",
    "                            [0, height-5],\n",
    "                            [width-5, height-5],\n",
    "                            [width-5, 0]])\n",
    "\n",
    "    for cnt in contours:\n",
    "        perimeter = cv2.arcLength(cnt, True)\n",
    "        approx = cv2.approxPolyDP(cnt, 0.03 * perimeter, True)\n",
    "\n",
    "        # Page has 4 corners and it is convex\n",
    "        if (len(approx) == 4 and\n",
    "                cv2.isContourConvex(approx) and\n",
    "                max_area < cv2.contourArea(approx) < MAX_COUNTOUR_AREA):\n",
    "            \n",
    "            max_area = cv2.contourArea(approx)\n",
    "            page_contour = approx[:, 0]\n",
    "\n",
    "    # Sort corners and offset them\n",
    "    page_contour = four_corners_sort(page_contour)\n",
    "    return contour_offset(page_contour, (-5, -5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PAGE CONTOUR:\n",
      "[[   0    1]\n",
      " [   0  799]\n",
      " [1146  798]\n",
      " [1145    0]]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 81344 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "page_contour = find_page_contours(edges_image, resize(image))\n",
    "print(\"PAGE CONTOUR:\")\n",
    "print(page_contour)\n",
    "implt(cv2.drawContours(resize(image), [page_contour], -1, (0, 255, 0), 3))\n",
    "\n",
    "       \n",
    "# Recalculate to original scale\n",
    "page_contour = page_contour.dot(ratio(image))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 81824 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def persp_transform(img, s_points):\n",
    "    \"\"\" Transform perspective from start points to target points \"\"\"\n",
    "    # Euclidean distance - calculate maximum height and width\n",
    "    height = max(np.linalg.norm(s_points[0] - s_points[1]),\n",
    "                 np.linalg.norm(s_points[2] - s_points[3]))\n",
    "    width = max(np.linalg.norm(s_points[1] - s_points[2]),\n",
    "                 np.linalg.norm(s_points[3] - s_points[0]))\n",
    "    \n",
    "    # Create target points\n",
    "    t_points = np.array([[0, 0],\n",
    "                        [0, height],\n",
    "                        [width, height],\n",
    "                        [width, 0]], np.float32)\n",
    "    \n",
    "    # getPerspectiveTransform() needs float32\n",
    "    if s_points.dtype != np.float32:\n",
    "        s_points = s_points.astype(np.float32)\n",
    "    \n",
    "    M = cv2.getPerspectiveTransform(s_points, t_points) \n",
    "    return cv2.warpPerspective(img, M, (int(width), int(height)))\n",
    "    \n",
    "    \n",
    "newImage = persp_transform(image, page_contour)\n",
    "#image = newImage\n",
    "#implt(newImage, t='Result')\n",
    "\n",
    "image = newImage\n",
    "implt(image, t='Result')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 79932 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "img = cv2.cvtColor(newImage, cv2.COLOR_RGB2GRAY)\n",
    "implt(img, 'gray')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 240,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 19572 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 15944 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def sobel(channel):\n",
    "    \"\"\" The Sobel Operator\"\"\"\n",
    "    sobelX = cv2.Sobel(channel, cv2.CV_16S, 1, 0)\n",
    "    sobelY = cv2.Sobel(channel, cv2.CV_16S, 0, 1)\n",
    "    # Combine x, y gradient magnitudes sqrt(x^2 + y^2)\n",
    "    sobel = np.hypot(sobelX, sobelY)\n",
    "    sobel[sobel > 255] = 255\n",
    "    return np.uint8(sobel)\n",
    "\n",
    "\n",
    "def edge_detect(im):\n",
    "    \"\"\" \n",
    "    Edge detection \n",
    "    The Sobel operator is applied for each image layer (RGB)\n",
    "    \"\"\"\n",
    "    return np.max(np.array([sobel(im[:,:, 0]), sobel(im[:,:, 1]), sobel(im[:,:, 2]) ]), axis=0)\n",
    "\n",
    "# Image pre-processing - blur, edges, threshold, closing\n",
    "#blurred = cv2.GaussianBlur(image, (1,1), 20)\n",
    "edges = edge_detect(image)\n",
    "ret, edges = cv2.threshold(edges, 50, 255, cv2.THRESH_BINARY)\n",
    "bw_image = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, np.ones((5, 4), np.uint8))\n",
    "\n",
    "implt(edges, 'gray', 'Sobel operator')\n",
    "implt(bw_image, 'gray', 'Final closing')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 235,
   "metadata": {},
   "outputs": [],
   "source": [
    "def union(a,b):\n",
    "    x = min(a[0], b[0])\n",
    "    y = min(a[1], b[1])\n",
    "    w = max(a[0]+a[2], b[0]+b[2]) - x\n",
    "    h = max(a[1]+a[3], b[1]+b[3]) - y\n",
    "    return [x, y, w, h]\n",
    "\n",
    "def intersect(a,b):\n",
    "    x = max(a[0], b[0])\n",
    "    y = max(a[1], b[1])\n",
    "    w = min(a[0]+a[2], b[0]+b[2]) - x\n",
    "    h = min(a[1]+a[3], b[1]+b[3]) - y\n",
    "    print(w)\n",
    "    if w<0 or h<0:\n",
    "        return False\n",
    "    return True\n",
    "\n",
    "def group_rectangles(rec):\n",
    "    \"\"\"\n",
    "    Uion intersecting rectangles\n",
    "    Args:\n",
    "        rec - list of rectangles in form [x, y, w, h]\n",
    "    Return:\n",
    "        list of grouped ractangles \n",
    "    \"\"\"\n",
    "    tested = [False for i in range(len(rec))]\n",
    "    final = []\n",
    "    i = 0\n",
    "    while i < len(rec):\n",
    "        if not tested[i]:\n",
    "            j = i+1\n",
    "            while j < len(rec):\n",
    "                if not tested[j] and intersect(rec[i], rec[j]):\n",
    "                    rec[i] = union(rec[i], rec[j])\n",
    "                    tested[j] = True\n",
    "                    j = i\n",
    "                j += 1\n",
    "            final += [rec[i]]\n",
    "        i += 1\n",
    "            \n",
    "    return final"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 236,
   "metadata": {},
   "outputs": [],
   "source": [
    "def text_detect(img, original):\n",
    "    \"\"\" Text detection using contours \"\"\"\n",
    "    # Resize image\n",
    "    small = resize(img, 2000)\n",
    "    image = resize(original, 2000)\n",
    "    cp_image = image.copy()\n",
    "    \n",
    "    # Finding contours\n",
    "    mask = np.zeros(small.shape, np.uint8)\n",
    "    cnt, hierarchy = cv2.findContours(np.copy(small), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)\n",
    "    \n",
    "    implt(img, 'gray')\n",
    "    \n",
    "    # Variables for contour index and words' bounding boxes\n",
    "    index = 0    \n",
    "    boxes = []\n",
    "    # CCOMP hierarchy: [Next, Previous, First Child, Parent]\n",
    "    # cv2.RETR_CCOMP - contours into 2 levels\n",
    "    # Go through all contours in first level\n",
    "    while (index >= 0):\n",
    "        x,y,w,h = cv2.boundingRect(cnt[index])\n",
    "        # Get only the contour\n",
    "        cv2.drawContours(mask, cnt, index, (255, 255, 255), cv2.FILLED)\n",
    "        maskROI = mask[y:y+h, x:x+w]\n",
    "        # Ratio of white pixels to area of bounding rectangle\n",
    "        r = cv2.countNonZero(maskROI) / (w * h)\n",
    "        \n",
    "        # Limits for text (white pixel ratio, width, height)\n",
    "        # TODO Test h/w and w/h ratios\n",
    "        if r > 0.1 and 2000 > w > 10 and 1600 > h > 10 and h/w < 3 and w/h < 10:\n",
    "            boxes += [[x, y, w, h]]\n",
    "        \n",
    "        # Index of next contour\n",
    "        index = hierarchy[0][index][0]\n",
    "        \n",
    "    # Group intersecting rectangles\n",
    "    #boxes = group_rectangles(boxes)\n",
    "    bounding_boxes = np.array([0,0,0,0])\n",
    "    for (x, y, w, h) in boxes:\n",
    "        cv2.rectangle(cp_image, (x, y),(x+w,y+h), (0, 255, 0), 8)\n",
    "        bounding_boxes = np.vstack((bounding_boxes, np.array([x, y, x+w, y+h])))\n",
    "\n",
    "    implt(cp_image, t='Bounding rectangles')\n",
    "\n",
    "    # Recalculate coordinates to original scale\n",
    "    boxes = bounding_boxes.dot(ratio(image, small.shape[0])).astype(np.int64)\n",
    "    return boxes[1:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 237,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 15352 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 74308 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of boxes: 131\n"
     ]
    }
   ],
   "source": [
    "boxes = text_detect(bw_image, image)\n",
    "print(\"Number of boxes:\", len(boxes))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 241,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sort_words(box):\n",
    "    boxes = box.copy()\n",
    "    \"\"\"Sort boxes - (x, y, x+w, y+h) from left to right, top to bottom.\"\"\"\n",
    "    mean_height = sum([y2 - y1 for _, y1, _, y2 in boxes]) / len(boxes)\n",
    "    \n",
    "    boxes.view('i8,i8,i8,i8').sort(order=['f1'], axis=0)\n",
    "    current_line = boxes[0][1]\n",
    "    lines = []\n",
    "    tmp_line = []\n",
    "    for box in boxes:\n",
    "        if box[1] > current_line + mean_height:\n",
    "            lines.append(tmp_line)\n",
    "            tmp_line = [box]\n",
    "            current_line = box[1]            \n",
    "            continue\n",
    "        tmp_line.append(box)\n",
    "    lines.append(tmp_line)\n",
    "        \n",
    "    for line in lines:\n",
    "        line.sort(key=lambda box: box[0])\n",
    "        \n",
    "    return lines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 251,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[array([ 21,  18, 104,  71]),\n",
       "  array([114,  27, 188,  72]),\n",
       "  array([199,  20, 294,  71]),\n",
       "  array([302,  22, 342,  77]),\n",
       "  array([353,  23, 466,  69]),\n",
       "  array([475,  15, 535,  79]),\n",
       "  array([545,  14, 670,  71]),\n",
       "  array([680,  27, 763,  70]),\n",
       "  array([773,  27, 887,  70]),\n",
       "  array([897,  27, 973,  73]),\n",
       "  array([ 982,   23, 1066,   69]),\n",
       "  array([1075,   25, 1158,   71])],\n",
       " [array([ 21, 103,  81, 152]),\n",
       "  array([ 90, 102, 185, 152]),\n",
       "  array([192, 104, 266, 150]),\n",
       "  array([277,  97, 388, 152]),\n",
       "  array([398,  97, 441, 152]),\n",
       "  array([449, 109, 490, 153]),\n",
       "  array([500, 104, 665, 153]),\n",
       "  array([674, 101, 779, 150]),\n",
       "  array([789, 109, 837, 151]),\n",
       "  array([849, 109, 946, 153]),\n",
       "  array([ 956,   97, 1038,  149]),\n",
       "  array([1048,   96, 1157,  151])],\n",
       " [array([ 18, 191,  40, 224]),\n",
       "  array([ 53, 184, 174, 242]),\n",
       "  array([185, 190, 226, 234]),\n",
       "  array([237, 185, 321, 234]),\n",
       "  array([344, 186, 427, 244]),\n",
       "  array([439, 185, 498, 234]),\n",
       "  array([509, 187, 625, 232]),\n",
       "  array([636, 198, 721, 241]),\n",
       "  array([733, 179, 837, 236]),\n",
       "  array([848, 191, 923, 236]),\n",
       "  array([ 935,  184, 1030,  234]),\n",
       "  array([1039,  179, 1158,  235])],\n",
       " [array([ 21, 266,  78, 314]),\n",
       "  array([ 89, 260, 231, 315]),\n",
       "  array([245, 268, 292, 314]),\n",
       "  array([304, 272, 335, 326]),\n",
       "  array([343, 270, 361, 302]),\n",
       "  array([366, 265, 461, 316]),\n",
       "  array([472, 260, 565, 316]),\n",
       "  array([577, 260, 688, 326]),\n",
       "  array([689, 272, 753, 314]),\n",
       "  array([768, 259, 856, 319]),\n",
       "  array([858, 278, 901, 317]),\n",
       "  array([910, 272, 963, 311]),\n",
       "  array([973, 268, 998, 312]),\n",
       "  array([1011,  267, 1082,  323]),\n",
       "  array([1095,  261, 1167,  322])],\n",
       " [array([ 23, 360,  98, 405]),\n",
       "  array([109, 348, 232, 400]),\n",
       "  array([244, 348, 334, 397]),\n",
       "  array([344, 352, 362, 384]),\n",
       "  array([366, 343, 450, 396]),\n",
       "  array([460, 361, 554, 397]),\n",
       "  array([564, 342, 676, 397]),\n",
       "  array([686, 342, 793, 408]),\n",
       "  array([795, 349, 882, 397]),\n",
       "  array([ 892,  344, 1018,  396]),\n",
       "  array([1031,  341, 1119,  401]),\n",
       "  array([1117,  360, 1159,  396])],\n",
       " [array([ 23, 430,  41, 464]),\n",
       "  array([ 36, 450,  82, 477]),\n",
       "  array([ 79, 454, 107, 477]),\n",
       "  array([120, 430, 197, 479]),\n",
       "  array([211, 433, 321, 472]),\n",
       "  array([245, 434, 257, 448]),\n",
       "  array([335, 435, 382, 477]),\n",
       "  array([398, 435, 506, 477]),\n",
       "  array([522, 430, 541, 465]),\n",
       "  array([536, 451, 582, 478]),\n",
       "  array([595, 430, 670, 479]),\n",
       "  array([684, 435, 729, 477]),\n",
       "  array([726, 431, 775, 478]),\n",
       "  array([790, 431, 874, 472]),\n",
       "  array([887, 424, 969, 479]),\n",
       "  array([ 981,  424, 1092,  489]),\n",
       "  array([1093,  435, 1157,  478])],\n",
       " [array([ 21, 511,  57, 557]),\n",
       "  array([ 53, 513, 101, 559]),\n",
       "  array([115, 508, 222, 570]),\n",
       "  array([239, 513, 281, 559]),\n",
       "  array([297, 505, 356, 568]),\n",
       "  array([372, 504, 488, 560]),\n",
       "  array([504, 514, 576, 548]),\n",
       "  array([592, 517, 634, 560]),\n",
       "  array([649, 517, 724, 563]),\n",
       "  array([740, 512, 824, 559]),\n",
       "  array([839, 514, 921, 560]),\n",
       "  array([937, 513, 974, 551]),\n",
       "  array([ 971,  517, 1036,  559]),\n",
       "  array([1051,  517, 1157,  559])],\n",
       " [array([ 21, 586, 163, 642]),\n",
       "  array([179, 595, 251, 629]),\n",
       "  array([188, 600, 202, 611]),\n",
       "  array([265, 593, 304, 629]),\n",
       "  array([300, 598, 365, 640]),\n",
       "  array([380, 586, 458, 640]),\n",
       "  array([473, 598, 511, 643]),\n",
       "  array([528, 598, 596, 635]),\n",
       "  array([609, 595, 694, 652]),\n",
       "  array([709, 598, 794, 643]),\n",
       "  array([809, 592, 904, 642]),\n",
       "  array([917, 592, 976, 643]),\n",
       "  array([ 990,  599, 1033,  642]),\n",
       "  array([1045,  594, 1156,  630])],\n",
       " [array([ 22, 666,  89, 733]),\n",
       "  array([ 97, 679, 138, 723]),\n",
       "  array([148, 672, 243, 723]),\n",
       "  array([252, 676, 311, 722]),\n",
       "  array([322, 674, 398, 725]),\n",
       "  array([409, 667, 671, 722]),\n",
       "  array([680, 671, 764, 724]),\n",
       "  array([774, 680, 848, 725]),\n",
       "  array([859, 673, 954, 723]),\n",
       "  array([ 962,  676, 1075,  722]),\n",
       "  array([1085,  666, 1161,  727])],\n",
       " [array([ 65, 751, 182, 807]),\n",
       "  array([182, 750, 270, 800]),\n",
       "  array([280, 761, 322, 805]),\n",
       "  array([332, 755, 404, 803]),\n",
       "  array([414, 759, 475, 797]),\n",
       "  array([487, 756, 563, 804]),\n",
       "  array([575, 749, 677, 805]),\n",
       "  array([688, 765, 805, 805]),\n",
       "  array([818, 770, 896, 808]),\n",
       "  array([ 906,  755, 1001,  805]),\n",
       "  array([1011,  762, 1088,  804]),\n",
       "  array([1100,  755, 1158,  812])]]"
      ]
     },
     "execution_count": 251,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted_boxes = sort_words(boxes)\n",
    "sorted_boxes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 243,
   "metadata": {},
   "outputs": [],
   "source": [
    "x1, y1, x2, y2 = sorted_boxes[0][-2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 244,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f2b6254f668>"
      ]
     },
     "execution_count": 244,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 11924 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.imshow(image[y1: y2, x1:x2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(823, 1183, 3)"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "image.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "268361"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import glob\n",
    "wikis = glob.glob('wiki/*.png')\n",
    "len(wikis)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'wiki/ternyata memberi.png'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wikis[-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f2b685bf780>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 8744 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "image_ = cv2.cvtColor(cv2.imread(wikis[-1]), cv2.COLOR_BGR2RGB)\n",
    "plt.imshow(image_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        ...,\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254]],\n",
       "\n",
       "       [[254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        ...,\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254]],\n",
       "\n",
       "       [[254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        ...,\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254]],\n",
       "\n",
       "       ...,\n",
       "\n",
       "       [[249, 249, 249],\n",
       "        [252, 252, 252],\n",
       "        [254, 254, 254],\n",
       "        ...,\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254]],\n",
       "\n",
       "       [[248, 248, 248],\n",
       "        [253, 253, 253],\n",
       "        [255, 255, 255],\n",
       "        ...,\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254]],\n",
       "\n",
       "       [[248, 248, 248],\n",
       "        [253, 253, 253],\n",
       "        [252, 252, 252],\n",
       "        ...,\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254],\n",
       "        [254, 254, 254]]], dtype=uint8)"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "image[y1: y2, x1:x2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 250,
   "metadata": {},
   "outputs": [],
   "source": [
    "class HysterThresh:    \n",
    "    def __init__(self, img):\n",
    "        img = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX)\n",
    "        img = 255 - img\n",
    "        img = (img - np.min(img)) / (np.max(img) - np.min(img)) * 255        \n",
    "        hist, bins = np.histogram(img.ravel(), 256, [0,256])\n",
    "        \n",
    "        self.high = np.argmax(hist) + 65\n",
    "        self.low = np.argmax(hist) + 45\n",
    "        self.diff = 255 - self.high\n",
    "        \n",
    "        self.img = img\n",
    "        self.im = np.zeros(img.shape, dtype=img.dtype)\n",
    "        self.hyster()\n",
    "        \n",
    "    def hyster_rec(self, r, c):\n",
    "        h, w = self.img.shape\n",
    "        for ri in [r-1, r+1]:\n",
    "            for ci in [c-1, c+1]:\n",
    "                if (h > ri >= 0\n",
    "                    and w > ci >= 0\n",
    "                    and self.im[ri, ci] == 0\n",
    "                    and self.high > self.img[ri, ci] >= self.low):                    \n",
    "                    self.im[ri, ci] = self.img[ri, ci] + self.diff\n",
    "                    self.hyster_rec(ri, ci)                \n",
    "                    \n",
    "    def hyster(self):\n",
    "        r, c = self.img.shape\n",
    "        for ri in range(r):\n",
    "            for ci in range(c):\n",
    "                if (self.img[ri, ci] >= self.high):\n",
    "                    self.im[ri, ci] = 255\n",
    "                    self.img[ri, ci] = 255\n",
    "                    self.hyster_rec(ri, ci)\n",
    "                    \n",
    "        implt(self.im, 'gray', 'Hister Thresh')\n",
    "\n",
    "\n",
    "def binary_otsu_norm(img):    \n",
    "    return cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]\n",
    "\n",
    "\n",
    "def bilateral_norm(img):\n",
    "    img = cv2.bilateralFilter(img, 9, 15, 30)\n",
    "    return cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX)\n",
    "\n",
    "\n",
    "def histogram_norm(img):\n",
    "    img = bilateral_norm(img)\n",
    "    add_img = 255 - cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]\n",
    "    img = 255 - img\n",
    "    img = (img - np.min(img)) / (np.max(img) - np.min(img)) * 255        \n",
    "    hist, bins = np.histogram(img.ravel(), 256, [0,256])\n",
    "    \n",
    "    img = img.astype(np.uint8)\n",
    "\n",
    "    ret,thresh4 = cv2.threshold(img,np.argmax(hist)+10,255,cv2.THRESH_TOZERO)\n",
    "    return add_img\n",
    "    return cv2.add(add_img, thresh4, dtype=cv2.CV_8UC1)\n",
    "\n",
    "def normalization2(img):    \n",
    "    implt(255 - img, 'gray', 'Original')   \n",
    "    implt(255 - bilateral_norm(img), 'gray', 'Bilateral')\n",
    "    implt(255 - binary_otsu_norm(img), 'gray', 'Binary OTSU')\n",
    "    implt(histogram_norm(img), 'gray', 'Binary OTSU + (Filter + TO_ZERO)')\n",
    "    HysterThresh(cv2.bilateralFilter(img, 10, 10, 30))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 248,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 0, 0, ..., 0, 0, 0],\n",
       "       [0, 0, 0, ..., 0, 0, 0],\n",
       "       [0, 0, 0, ..., 0, 0, 0],\n",
       "       ...,\n",
       "       [0, 0, 0, ..., 0, 0, 0],\n",
       "       [0, 0, 0, ..., 0, 0, 0],\n",
       "       [0, 0, 0, ..., 0, 0, 0]], dtype=uint8)"
      ]
     },
     "execution_count": 248,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "histogram_norm(image[y1: y2, x1:x2, 0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 249,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10260 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "implt(histogram_norm(image[y1: y2, x1:x2, 0]), 'gray', 'Binary OTSU + (Filter + TO_ZERO)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 8112 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "implt(cv2.bitwise_not(image_))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 227,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.ndimage.interpolation import map_coordinates\n",
    "from scipy.ndimage.filters import gaussian_filter\n",
    "\n",
    "def elastic_transform(image, alpha, sigma, alpha_affine, random_state=None):\n",
    "    if random_state is None:\n",
    "        random_state = np.random.RandomState(None)\n",
    "\n",
    "    shape = image.shape\n",
    "    shape_size = shape[:2]\n",
    "    \n",
    "    blur_size = int(4*sigma) | 1\n",
    "    dx = alpha * cv2.GaussianBlur((random_state.rand(*shape) * 2 - 1),\n",
    "                                  ksize=(blur_size, blur_size),\n",
    "                                  sigmaX=sigma)\n",
    "    dy = alpha * cv2.GaussianBlur((random_state.rand(*shape) * 2 - 1),\n",
    "                                  ksize=(blur_size, blur_size),\n",
    "                                  sigmaX=sigma)\n",
    "\n",
    "    x, y = np.meshgrid(np.arange(shape[1]), np.arange(shape[0]))\n",
    "    indices = np.reshape(y+dy, (-1, 1)), np.reshape(x+dx, (-1, 1))\n",
    "\n",
    "    image =  map_coordinates(image, indices, order=1, mode='constant').reshape(shape)\n",
    "    \n",
    "    implt(image, 'gray')\n",
    "    \n",
    "    # Random affine\n",
    "    center_square = np.float32(shape_size) // 2\n",
    "    print(center_square)\n",
    "    square_size = min(shape_size) // 4\n",
    "    pts1 = np.float32([center_square + square_size,\n",
    "                       [center_square[0]+square_size, \n",
    "                        center_square[1]-square_size],\n",
    "                       center_square - square_size])\n",
    "    pts2 = pts1 + random_state.uniform(-alpha_affine, alpha_affine, size=pts1.shape).astype(np.float32)\n",
    "    M = cv2.getAffineTransform(pts1, pts2)\n",
    "    image = cv2.warpAffine(image, M, shape_size[::-1], borderMode=cv2.BORDER_CONSTANT)\n",
    "\n",
    "    return image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 228,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 19348 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 66. 241.]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 21104 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "im = cv2.bitwise_not(image_)[:,:,0]\n",
    "im = im[:, np.min(np.where(im > 0)[1]) - 10:]\n",
    "implt(elastic_transform(im, im.shape[1] * 5, im.shape[1] * 0.2, im.shape[1] * 0.001), 'gray')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
