{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 191032 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import argparse\n",
    "import imutils\n",
    "import cv2\n",
    "import os\n",
    "import matplotlib.pyplot as plt\n",
    "import doxapy\n",
    "import sys\n",
    "import time\n",
    "\n",
    "inputfile = '/home/elena/Documents/Projects/kassenzettel-django-react/testings/receipe-upload-_oqp9us3.jpg'\n",
    "#inputfile = '/home/elena/Documents/Projects/kassenzettel-django-react/testings/testbild.png'\n",
    "img = cv2.imread(inputfile, cv2.IMREAD_GRAYSCALE)\n",
    "start = 200\n",
    "stop = start + 700\n",
    "\n",
    "img_snippet = img[start:stop,:]\n",
    "img_copy = cv2.cvtColor(img_snippet,cv2.COLOR_GRAY2RGB)\n",
    "\n",
    "binary_image = np.empty(img_snippet.shape, img_snippet.dtype)\n",
    "sauvola = doxapy.Binarization(doxapy.Binarization.Algorithms.ISAUVOLA)\n",
    "sauvola.initialize(img_snippet)\n",
    "sauvola.to_binary(binary_image, {\"window\": 45, \"k\": 0.009})\n",
    "\n",
    "#Plot the snippet\n",
    "plt.figure(figsize=(10,10))\n",
    "plt.imshow(binary_image, cmap='gray')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "307\n"
     ]
    }
   ],
   "source": [
    "#Invert the binary image\n",
    "binary_image = cv2.bitwise_not(binary_image)\n",
    "# Calc connected components\n",
    "num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image, connectivity=8)\n",
    "\n",
    "# Calculate the mean vector and the covariance matrix of the pixels in each connected component\n",
    "means = np.empty((num_labels,2))\n",
    "covs = np.empty((num_labels,2,2))\n",
    "eigenvalues = np.empty((num_labels,2))\n",
    "eigenvectors = np.empty((num_labels,2,2))\n",
    "\n",
    "labelsForDelete = []\n",
    "\n",
    "for i in range(1,num_labels):\n",
    "    mask = np.zeros(binary_image.shape, np.uint8)\n",
    "    mask[labels == i] = 255\n",
    "    \n",
    "    xy_coords = np.flip(np.column_stack(np.where(mask == 255)), axis=1)\n",
    "    #means[i] = np.mean(xy_coords, axis=0)  # Is equal to the centroid\n",
    "    covs[i] = np.cov(xy_coords, rowvar=False)\n",
    "\n",
    "    # Calculate the eienvalues and eigenvectors of the covariance matrix\n",
    "    eigenvalues[i], eigenvectors[i] = np.linalg.eig(covs[i])\n",
    "\n",
    "    #Sort the eigenvalues and eigenvectors\n",
    "    idx = eigenvalues[i].argsort()[::-1]\n",
    "    eigenvalues[i] = eigenvalues[i][idx]\n",
    "    eigenvectors[i] = eigenvectors[i][:,idx]\n",
    "\n",
    "    #If eigenvalue1 / eigenvalue2 > 15, remove the corresponding connected component\n",
    "    if eigenvalues[i,0] / eigenvalues[i,1] > 15**2:\n",
    "        labelsForDelete.append(i)\n",
    "\n",
    "\n",
    "#Delete the connected components in list\n",
    "labels = np.delete(labels, labelsForDelete, axis=0)\n",
    "num_labels = num_labels - len(labelsForDelete)\n",
    "stats = np.delete(stats, labelsForDelete, axis=0)\n",
    "centroids = np.delete(centroids, labelsForDelete, axis=0)\n",
    "eigenvalues = np.delete(eigenvalues, labelsForDelete, axis=0)\n",
    "eigenvectors = np.delete(eigenvectors, labelsForDelete, axis=0)\n",
    "\n",
    "# Create a new image with only the ellipses\n",
    "ellipseMask = np.zeros(binary_image.shape, np.uint8)\n",
    "for i in range(1,num_labels):\n",
    "    # Plot eigenvalues as ellipses in black and white image\n",
    "    cv2.ellipse(ellipseMask,(int(centroids[i,0]),int(centroids[i,1])),(int(np.sqrt(eigenvalues[i,0])),int(np.sqrt(eigenvalues[i,1]))),np.rad2deg(np.arctan2(eigenvectors[i,0,1],eigenvectors[i,0,0])),0,360,(255,255,255),-1)\n",
    "\n",
    "height, width = ellipseMask.shape[:2] \n",
    "# get the center coordinates of the image to create the 2D rotation matrix \n",
    "center = (width/2, height/2) \n",
    "# Get diagonal of the image\n",
    "diagonal = np.sqrt(width**2 + height**2)\n",
    "\n",
    "# Pad the ellipseMask with zeros to around the center to fit the diagonal\n",
    "ellipseMask = np.pad(ellipseMask, ((int(diagonal/2 - height/2),int(diagonal/2 - height/2)),(int(diagonal/2 - width/2),int(diagonal/2 - width/2))), 'constant', constant_values=(0,0))\n",
    "heightPadded, widthPadded = ellipseMask.shape[:2] \n",
    "centerPadded = (widthPadded/2, heightPadded/2) \n",
    "\n",
    "# Shift centroids by padded amount\n",
    "centroids = centroids + (diagonal/2 - width/2, diagonal/2 - height/2)\n",
    "\n",
    "print(num_labels)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def changes_in_bitoperation(sequence: np.ndarray) -> int:\n",
    "    '''\n",
    "    Count the number of bit flips in a binary sequence (2x faster than itertools solution), adopten from: \n",
    "    https://stackoverflow.com/questions/70300297/is-there-a-built-in-python-function-to-count-bit-flip-in-a-binary-string#:~:text=There%20is%20a%20very%20fast%20way%20to%20do,string%20to%20count%20the%20number%20of%20bit%20flips.\n",
    "    :param sequence: A binary sequence as a numpy array\n",
    "    :return: The number of bit flips\n",
    "    '''\n",
    "    # Convert the sequence to an integer: [0,1,1,0,1] -> 0b01101\n",
    "    s = np.array2string(sequence, separator='',max_line_width=1000000)\n",
    "    n = int(s[1:-1], 2)\n",
    "\n",
    "    # Build a binary mask to skip the most significant bit of n: 0b01101 -> 0b01111\n",
    "    mask = (1 << (len(s)-1)) - 1\n",
    "\n",
    "    # Check if the ith bit of n is different from the (i+1)th bit of n using a bit-wise XOR:\n",
    "    # 0b01101 & 0b01111 -> 0b1101  (discard the first bit)\n",
    "    # 0b01101 >> 1      -> 0b0110\n",
    "    # 0b1101 ^ 0b0110   -> 0b1011\n",
    "    bitFlips = (n & mask) ^ (n >> 1)\n",
    "\n",
    "    # Convert the integer back to a string and count the bit flips: 0b1011 -> \"0b1011\" -> 3\n",
    "    flipCount = bin(bitFlips).count('1')\n",
    "    return flipCount"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def changes_in_bitoperation_v2(sequence: np.ndarray) -> int:\n",
    "    '''\n",
    "    Count the number of bit flips in a binary sequence (2x faster than itertools solution), adopten from: \n",
    "    https://stackoverflow.com/questions/70300297/is-there-a-built-in-python-function-to-count-bit-flip-in-a-binary-string#:~:text=There%20is%20a%20very%20fast%20way%20to%20do,string%20to%20count%20the%20number%20of%20bit%20flips.\n",
    "    :param sequence: A binary sequence as a numpy array\n",
    "    :return: The number of bit flips\n",
    "    '''\n",
    "    # Convert the sequence to an integer: [0,1,1,0,1] -> 0b01101\n",
    "    #s = np.array2string(sequence, separator='',max_line_width=1000000)\n",
    "    # = int(s[1:-1], 2)\n",
    "    s = sequence.tobytes()\n",
    "    n = int.from_bytes(s, byteorder='big', signed=False)\n",
    "\n",
    "    # Build a binary mask to skip the most significant bit of n: 0b01101 -> 0b01111\n",
    "    mask = (1 << (len(s)*8-1)) - 1\n",
    "\n",
    "    # Check if the ith bit of n is different from the (i+1)th bit of n using a bit-wise XOR:\n",
    "    # 0b01101 & 0b01111 -> 0b1101  (discard the first bit)\n",
    "    # 0b01101 >> 1      -> 0b0110\n",
    "    # 0b1101 ^ 0b0110   -> 0b1011\n",
    "    bitFlips = (n & mask) ^ (n >> 1)\n",
    "\n",
    "    # Convert the integer back to a string and count the bit flips: 0b1011 -> \"0b1011\" -> 3\n",
    "    flipCount = bin(bitFlips).count('1')\n",
    "    if sequence[0] == 255:\n",
    "        flipCount += 1\n",
    "        \n",
    "    return flipCount"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"\\n#np.random.seed(14)\\nimport time\\nimport binascii\\nfrom scipy.ndimage import label\\n\\nsequence = np.random.randint(0,2,1000).astype(np.uint8)\\ns = np.array2string(sequence, separator='',max_line_width=1000000)\\nn = int(s[1:-1], 2)\\nprint(s)\\nprint(n)\\n\\ns2 = sequence * 255\\nbinary_str = s2.tobytes()\\nprint(binary_str)\\n\\n\\n# Convert the binary string to an integer\\nint_val = int.from_bytes(binary_str, byteorder='big', signed=False)\\n#print(int.from_bytes(binary_str, byteorder='big', signed=False))\\n#print(int.from_bytes(binary_str, byteorder='little', signed=False))\\nprint(bin(int_val))\\n#sequence = (ellipseMask[:,100]/255).astype(np.uint8)\\n#print(sequence.shape)\\n\\n\\nmeanTime = 0\\nfor i in range(0,1000):\\n    start = time.time()\\n    a = changes_in_bitoperation(sequence)\\n    end = time.time()\\n    meanTime += end - start\\n\\nprint(a)\\nmeanTime = meanTime / 100\\nprint(meanTime)\\n\\nsequence = sequence * 255\\nmeanTime = 0\\nfor i in range(0,1000):\\n    start = time.time()\\n    a = changes_in_bitoperation_v2(sequence)\\n    end = time.time()\\n    meanTime += end - start\\n\\nprint(a)\\nmeanTime = meanTime / 100\\nprint(meanTime)\\n\\n\\nmeanTime = 0\\nfor i in range(0,1000):\\n    start = time.time()\\n\\n    flipCount = 0\\n\\n    s = sequence.tobytes()\\n    n = int.from_bytes(s, byteorder='big', signed=False)\\n    mask = (1 << (len(s*8)-1)) - 1\\n\\n    bitFlips = (n & mask) ^ (n >> 1)\\n\\n    flipCount += bin(bitFlips).count('1')\\n    if sequence[0] == 255:\\n        flipCount += 1\\n    end = time.time()\\n    meanTime += end - start\\n    \\n\\nprint(flipCount)\\nmeanTime = meanTime / 100\\nprint(meanTime)\\n\""
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# For debugging\n",
    "# Generate a random array of 0s and 1s with length 10000 always with the same seed\n",
    "'''\n",
    "#np.random.seed(14)\n",
    "import time\n",
    "import binascii\n",
    "from scipy.ndimage import label\n",
    "\n",
    "sequence = np.random.randint(0,2,1000).astype(np.uint8)\n",
    "s = np.array2string(sequence, separator='',max_line_width=1000000)\n",
    "n = int(s[1:-1], 2)\n",
    "print(s)\n",
    "print(n)\n",
    "\n",
    "s2 = sequence * 255\n",
    "binary_str = s2.tobytes()\n",
    "print(binary_str)\n",
    "\n",
    "\n",
    "# Convert the binary string to an integer\n",
    "int_val = int.from_bytes(binary_str, byteorder='big', signed=False)\n",
    "#print(int.from_bytes(binary_str, byteorder='big', signed=False))\n",
    "#print(int.from_bytes(binary_str, byteorder='little', signed=False))\n",
    "print(bin(int_val))\n",
    "#sequence = (ellipseMask[:,100]/255).astype(np.uint8)\n",
    "#print(sequence.shape)\n",
    "\n",
    "\n",
    "meanTime = 0\n",
    "for i in range(0,1000):\n",
    "    start = time.time()\n",
    "    a = changes_in_bitoperation(sequence)\n",
    "    end = time.time()\n",
    "    meanTime += end - start\n",
    "\n",
    "print(a)\n",
    "meanTime = meanTime / 100\n",
    "print(meanTime)\n",
    "\n",
    "sequence = sequence * 255\n",
    "meanTime = 0\n",
    "for i in range(0,1000):\n",
    "    start = time.time()\n",
    "    a = changes_in_bitoperation_v2(sequence)\n",
    "    end = time.time()\n",
    "    meanTime += end - start\n",
    "\n",
    "print(a)\n",
    "meanTime = meanTime / 100\n",
    "print(meanTime)\n",
    "\n",
    "\n",
    "meanTime = 0\n",
    "for i in range(0,1000):\n",
    "    start = time.time()\n",
    "\n",
    "    flipCount = 0\n",
    "\n",
    "    s = sequence.tobytes()\n",
    "    n = int.from_bytes(s, byteorder='big', signed=False)\n",
    "    mask = (1 << (len(s*8)-1)) - 1\n",
    "\n",
    "    bitFlips = (n & mask) ^ (n >> 1)\n",
    "\n",
    "    flipCount += bin(bitFlips).count('1')\n",
    "    if sequence[0] == 255:\n",
    "        flipCount += 1\n",
    "    end = time.time()\n",
    "    meanTime += end - start\n",
    "    \n",
    "\n",
    "print(flipCount)\n",
    "meanTime = meanTime / 100\n",
    "print(meanTime)\n",
    "'''\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calcProjection(Np: int, centroid: np.ndarray, ellipseMask: np.ndarray) -> np.ndarray:\n",
    "    '''\n",
    "    Calculates the projection of the ellipse mask\n",
    "    :param Np: Number of pixels in the projection\n",
    "    :param centroid: Centroid of the ellipse mask\n",
    "    :param ellipseMask: Ellipse mask\n",
    "    :return: Projection of the ellipse mask\n",
    "    '''\n",
    "    xprojection = np.zeros(Np)\n",
    "    paddedMask = np.zeros((ellipseMask.shape[0],Np), np.uint8)\n",
    "\n",
    "    if centroid[0]-Np/2 < 0 and centroid[0]+Np/2 < ellipseMask.shape[1]:\n",
    "        #Pad at the beginning\n",
    "        paddedMask = np.pad(ellipseMask, ((0,0),(int(Np/2 - centroid[0]),0)), 'reflect')[:,0:Np]\n",
    "\n",
    "    elif centroid[0]-Np/2 > 0 and centroid[0]+Np/2 > ellipseMask.shape[1]:\n",
    "        #Pad at the end\n",
    "        paddedMask = np.pad(ellipseMask, ((0,0),(0,int(Np/2 - (ellipseMask.shape[1] - centroid[0])))), 'reflect')[:,0:Np]\n",
    "    elif centroid[0]-Np/2 < 0 and centroid[0]+Np/2 > ellipseMask.shape[1]:\n",
    "        #Pad at the beginning and at the end\n",
    "        paddedMask = np.pad(ellipseMask, ((0,0),(int(Np/2 - centroid[0]),int(Np/2 - (ellipseMask.shape[1] - centroid[0])))), 'reflect')[:,0:Np]\n",
    "    else:\n",
    "        #No padding\n",
    "        paddedMask = ellipseMask[:,int(centroid[0]-Np/2):int(centroid[0]+Np/2)]\n",
    "\n",
    "    #Calculate the projection\n",
    "    for rowNumber in range(0,paddedMask.shape[1]):\n",
    "        row = paddedMask[:,rowNumber]\n",
    "        flipps = changes_in_bitoperation_v2(row)\n",
    "\n",
    "        if row[0] > 0 and row[-1] == 0:\n",
    "            flipps += 1\n",
    "        elif row[-1] > 0 and row[0] == 0:\n",
    "            flipps += 1\n",
    "        elif row[0] > 0 and row[-1] > 0:\n",
    "            flipps += 2\n",
    "\n",
    "        xprojection[rowNumber] = flipps/2\n",
    "\n",
    "    return xprojection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 24136 characters]",
      "text/plain": [
       "<Figure size 1000x200 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Iterate over each connected component\n",
    "D = 32\n",
    "Np = 256\n",
    "kp = 5\n",
    "epsilon = 3\n",
    "\n",
    "Vp = np.zeros((num_labels,D))\n",
    "\n",
    "angles = np.pi/32 * np.arange(0,D)\n",
    "\n",
    "for angleNum in range(0,angles.shape[0]):\n",
    "    # Calculate the rotation matrix\n",
    "    M = cv2.getRotationMatrix2D(center=centerPadded, angle=np.rad2deg(angles[angleNum]),scale=1)\n",
    "    rotated = cv2.warpAffine(ellipseMask,M,(ellipseMask.shape[1],ellipseMask.shape[0]))\n",
    "    #Rotate all centroids by angle\n",
    "    rotated_centroids = np.dot(M,np.append(centroids, np.ones((num_labels,1)), axis=1).T).T[:,:2]\n",
    "\n",
    "    _, rotated = cv2.threshold(rotated, 127, 255, cv2.THRESH_BINARY)\n",
    "\n",
    "    for centroidNum in range(100,101):#,num_labels):\n",
    "        #start = time.time()\n",
    "        xprojection = calcProjection(Np, rotated_centroids[centroidNum] , rotated)\n",
    "        #end = time.time()\n",
    "        #print(end-start)\n",
    "\n",
    "        #start = time.time()\n",
    "        #Check if xprojection is smaler or equal than 3 for all values\n",
    "        if np.all(xprojection <= 3):\n",
    "            Vp[centroidNum,angleNum] = epsilon\n",
    "        else:\n",
    "            # Calculate the rfft of the xprojection\n",
    "            dft = np.fft.rfft(xprojection)\n",
    "            #print(dft)\n",
    "\n",
    "            #Check if dft[kp] is local maximum\n",
    "            if kp > 0 and kp < len(dft)-1:\n",
    "                if np.abs(dft[kp]) > np.abs(dft[kp-1]) and np.abs(dft[kp]) > np.abs(dft[kp+1]):\n",
    "                    Vp[centroidNum,angleNum] = -np.log(np.abs(dft[kp])**2/np.abs(dft[0])**2)\n",
    "                else:\n",
    "                    Vp[centroidNum,angleNum] = epsilon\n",
    "        #stop = time.time()\n",
    "        #print(stop-start)\n",
    "\n",
    "        \n",
    "#Plot Vp\n",
    "plt.figure(figsize=(10,2))\n",
    "plt.plot(angles,Vp[100,:])\n",
    "plt.show()\n",
    "\n",
    "        \n"
   ]
  },
  {
   "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.11.5"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
