Spaces:
Runtime error
Runtime error
File size: 8,514 Bytes
e5b2779 f169836 e5b2779 25afb11 e5b2779 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
import streamlit as st
import numpy as np
import pandas as pd
from PIL import Image
import io
import cv2 as cv
import pytesseract
min_size_of_cell = st.sidebar.slider('Min. size of Cell', 1, 50000, 5000)
st.sidebar.write("Adjust this setting so that no text gets selected and not too large that any cell will be missed.")
table_contour_factor = st.sidebar.slider('Table Contour Factor', 1, 100, 10)
st.sidebar.write("Adjust this setting so that the border of entire table / image is not selected. Also not too large that any cell will be missed.")
if 'significant_contour_list' not in st.session_state:
st.session_state.significant_contour_list = []
if 'imgray' not in st.session_state:
st.session_state.imgray = 0
if 'df' not in st.session_state:
st.session_state.df = pd.DataFrame()
def remove_newline_char(a):
if a == "":
return ""
if str(a) == "NaN":
return ""
if a[-1] == '\n':
return a[:-1]
return a
def convert_DF_to_csv(df):
s = ""
for i in range(0,df.shape[0]):
for j in range(0,df.shape[1]):
if j == df.shape[1] - 1:
s = s + str(df.iloc[i,j])
else:
s = s + str(df.iloc[i,j]) + ","
s = s + '\n'
return s
def runalgo():
# now for easy of computing and establishing regions for text mining each signifiant contour, their respective bounding rectangular boxes are found.
significant_contour_list = st.session_state.significant_contour_list
significant_contour_rect_details = []
imgray = st.session_state.imgray
for i in range(0,len(significant_contour_list)):
significant_contour_rect_details.append(cv.boundingRect(significant_contour_list[i]))
# the center of each rect for each cell is computed to further easy in sorting and finding the order of cells.
significant_contour_rect_center = []
for i in range(0,len(significant_contour_rect_details)):
significant_contour_rect_center.append((significant_contour_rect_details[i][0] +
significant_contour_rect_details[i][2] / 2,
significant_contour_rect_details[i][1] +
significant_contour_rect_details[i][3] / 2,
i))
# since the order of contours can be different and the exact no. of rows and columns are always unclear
# 1. the contour with least y value is found
# 2. then the header row is figured out by comparing the y value of each cell with the least y value
# 3. still the header row may not be in a correct sequence hence they are ordered by x value to represent the header row of a flat table.
unordered_header_rows = []
min_y = 1000000.0
min_index = 0
for i in range(0,len(significant_contour_rect_center)):
if min_y >= significant_contour_rect_center[i][1]:
min_y = significant_contour_rect_center[i][1]
min_index = i
for i in range(0,len(significant_contour_rect_center)):
if abs(min_y - significant_contour_rect_center[i][1]) <= 5:
unordered_header_rows.append(i)
header_rows_x_values_unordered = []
for i in range(0,len(unordered_header_rows)):
header_rows_x_values_unordered.append(significant_contour_rect_center[unordered_header_rows[i]][0])
header_rows_x_values_index = np.argsort(header_rows_x_values_unordered)
header_rows_index = []
for i in range(0,len(header_rows_x_values_index)):
header_rows_index.append(unordered_header_rows[header_rows_x_values_index[i]])
# now from ordered header row cells the remaining cells that are vertically below are found out and then they are ordered by y value.
table_cells_index = []
for i in header_rows_index:
table_cells_index.append([i])
for i in range(0,len(header_rows_index)):
for j in range(0,len(significant_contour_rect_center)):
if abs(significant_contour_rect_center[j][0] -
significant_contour_rect_center[header_rows_index[i]][0]) <= 5 and j != header_rows_index[i]:
table_cells_index[i].append(j)
for i in range(0,len(header_rows_index)):
a = list(table_cells_index[i][1:])
col_y = []
for j in a:
col_y.append(significant_contour_rect_center[j][1])
col_y_index = np.argsort(col_y)
col_y_index = col_y_index
b = []
for j in col_y_index:
b.append(a[j])
table_cells_index[i] = [header_rows_index[i]] + b
# for ech cell tesseract is used to extract the text and stored in a 2d list.
# pytesseract.pytesseract.tesseract_cmd = "/opt/homebrew/Cellar/tesseract/5.3.0_1/bin/tesseract" #this is must for macOS M1
table_contents = []
for i in range(0,len(table_cells_index)):
a = []
for j in table_cells_index[i]:
y = significant_contour_rect_details[j][1]
h = significant_contour_rect_details[j][3]
x = significant_contour_rect_details[j][0]
w = significant_contour_rect_details[j][2]
cropped = imgray[y:y + h, x:x + w]
text = pytesseract.image_to_string(cropped)
a.append(text)
table_contents.append(a)
df = pd.DataFrame(table_contents)
df = df.transpose() # since the data is column wise we have to apply transpose to convert to a flat table.
# some preprocessing is required like removing new line character at the last for each cell in the dataframe.
for i in range(0,len(df.columns)):
df[i] = df.apply(lambda x: remove_newline_char(x[i]),axis = 1)
st.session_state.df = df
def contour_area(a):
return cv.contourArea(a)
def setCountours(img_bytes):
imgray = cv.cvtColor(img_bytes, cv.COLOR_BGR2GRAY)
st.session_state.imgray = imgray
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# creating a list of areas by each contour
contour_area_list = []
for i in range(0,len(contours)):
contour_area_list.append(contour_area(contours[i]))
contour_area_list = np.array(contour_area_list)
# finding only significant_counters -- here the area is used as metric to eliminate text contours and other small regions
significant_contour_list = []
max_contour_area = max(contour_area_list)
for i in range(0,len(contours)):
# here it is assumed that each cell int able be atleast 800 sq. pixels
# there is always a possiblity of non exact crop of image hence there will always be atleast 1 large contour around the table border.
if contour_area_list[i] > min_size_of_cell and contour_area_list[i] < max_contour_area / table_contour_factor:
significant_contour_list.append(contours[i])
significant_contour_list = np.array(significant_contour_list)
st.session_state.significant_contour_list = significant_contour_list
im_contours_significant = img_bytes.copy()
im_contours_significant = cv.drawContours(im_contours_significant, significant_contour_list, -1, (0,255,0), 3) # the contours are set to be visible in green
img = cv.cvtColor(im_contours_significant, cv.COLOR_BGR2RGB)
im_pil = Image.fromarray(img)
return im_pil
def convertImg(img):
nparr = np.array(img.convert('RGB'))
return nparr[:, :, ::-1].copy()
st.title("Table from Image using opencv")
image = Image.open('sports_data.png')
image_contoured = setCountours(convertImg(image))
info_placeholder = st.empty()
# tab1, tab2 = st.tabs(["Data","Contoured Image"])
# upload_image_button = st.button("Upload Image")
uploaded_file = st.file_uploader("Upload Image",type=['png'])
if uploaded_file is not None:
bytes_data = uploaded_file.getvalue()
image = Image.open(io.BytesIO(bytes_data))
image_contoured = setCountours(convertImg(image))
st.sidebar.header("Original Image")
st.sidebar.image(image)
col_b_1, col_b_2 = st.columns(2)
with col_b_1:
st.button("Convert",on_click=runalgo)
with col_b_2:
st.download_button('Download CSV', convert_DF_to_csv(st.session_state.df), file_name='data.csv')
col1, col2 = st.columns(2)
with col2:
if st.session_state.df.shape[0] != 0:
st.header("Data")
st.dataframe(st.session_state.df)
with col1:
st.header("Image with Contours")
st.image(image_contoured) |