import re import cv2 import numpy as np from paddleocr import PaddleOCR from PIL import Image import matplotlib.pyplot as plt import pandas as pd import matplotlib.pyplot as plt import onnxruntime import gradio as gr # initialize the OCR ocr = PaddleOCR(lang='sl', enable_mkldnn=True, cls=False, show_log= False) # initialize the models model_deskew = onnxruntime.InferenceSession("./models/CNN_deskew_v0.0.2.onnx") model_denoise = onnxruntime.InferenceSession("./models/autoencoder_denoise_v0.0.2.onnx") ##### All Functions ##### def preprocess_image(image): ''' Function: preprocess image to make it lighter to work on Input: resized image Output: image ''' image = np.array(image) scale = 1.494 width = int(image.shape[1] / scale) height = int(image.shape[0] / scale) dim = (width, height) image = cv2.resize(image, dim, interpolation = cv2.INTER_AREA) return image def deskew(image, model): ''' Function: deskew an image Input: takes an image as an array Output: deskewed image ''' # map the model classes to the actual degree of skew map = { 0: '-1', 1: '-10', 2: '-11', 3: '-12', 4: '-13', 5: '-14',6: '-15', 7: '-2', 8: '-3', 9: '-4', 10: '-5',11: '-6',12: '-7', 13: '-8', 14: '-9', 15: '0', 16: '1', 17: '10', 18: '11', 19: '12', 20: '13',21: '14',22: '15', 23: '180',24: '2', 25: '270',26: '3',27: '4', 28: '5', 29: '6', 30: '7', 31: '8',32: '9', 33: '90'} image_d = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) width = int(image_d.shape[1] * 0.2) height = int(image_d.shape[0] * 0.2) dim = (width, height) # resize image res = cv2.resize(image_d, dim, interpolation = cv2.INTER_AREA) resized = cv2.resize(res, (200, 200)) # add two dimensions to feed to the model resized = resized.astype('float32').reshape(1, 200, 200 ,1) # normalize resized = resized/255 # predictions predictions = model.run(None, {'conv2d_input': resized}) # best prediction pred = predictions[0].argmax() # angle of skew angle = int(map[pred]) skew_confidence = predictions[0][0][pred] * 100 # deskew original image if angle == 90: deskewed_image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE) return deskewed_image, angle, skew_confidence if angle == 270: deskewed_image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE) return deskewed_image, angle, skew_confidence (h, w) = image.shape[:2] center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, -angle, 1.0) deskewed_image = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) return deskewed_image, angle, skew_confidence def prepare_image_to_autoencoder(image): ''' Function: prepare the image to be passed to the autoencoder. Input: image (_type_): deskewed image Output: resized image to be passed to the autoencoder ''' height, width = image.shape[:2] target_height = 600 target_width = 600 image = image[int(height/3.6): int(height/1.87), int(width/3.67): int(width/1.575)] # reshape image to fixed size image = cv2.resize(image, (target_width, target_height)) image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # normalize images image = image / 255.0 # reshape to pass image to autoencoder image = image.reshape(target_height, target_width, 1) return image def autoencode_ONNX(image, model): ''' Function: remove noise from image Input: image and autoencoder model Output: image ''' image = image.astype(np.float32).reshape(1, 600, 600, 1) image = model.run(None, {'input_2': image}) image = image[0] image = image.squeeze() image = image * 255 image = image.astype('uint8') return image def extract_detected_entries_pdl(image): """ Extracts text, scores, and boundary boxes from an image using OCR and returns a DataFrame. This function takes an input image, applies OCR to detect text in the image, and then extracts the detected text, confidence scores, and boundary boxes for each text entry. The extracted information is returned in a DataFrame with columns "Text", "Score", and "Boundary Box". Parameters ---------- image : numpy.ndarray The input image to be processed. Returns ------- pandas.DataFrame A DataFrame containing the extracted text, confidence scores, and boundary boxes for each detected text entry. The DataFrame has the following columns: - "Text": The detected text. - "Score": The confidence score for the detected text. - "Boundary Box": The coordinates of the boundary box for the detected text. """ # run the OCR result = ocr.ocr(image) # creates Pandas Dataframe txt = [] scores = [] boxes = [] for r in result[0]: txt.append(cleanString_basic(r[-1][0])) scores.append(r[-1][1]) boxes.append(tuple(map(tuple, r[0]))) return pd.DataFrame({"Text": txt, "Score": scores, "Boundary Box": boxes}) def cleanString_basic(word): word = word.replace("$", "s") return word def clean_string_start(string: 'str'): names_flags = "√" chars_to_remove = ['!', "'", '[', ']', '*', '|', '.', ':', '\\', '/'] if string.startswith(tuple(chars_to_remove)): names_flags = string[0] string = string[1:] return string, names_flags def clean_string_end(string: 'str'): names_flags = "√" chars_to_remove = ['!', "'", '[', ']', '*', '|', '.', ':', '\\', '/'] if string.endswith(tuple(chars_to_remove)): names_flags = string[-1] string = string[:-1] return string, names_flags def clean_dates(date: 'str'): ''' Function: cleans the fields "datum smrti" and returns the char removed. Input: date (string format) Output: cleaned frame ''' date_flags = "Y" # finds special characters in the string special_char = re.findall(r'[a-zA-Z!\[\|]', date) if len(special_char) > 0: date_flags = special_char # remove special characters in the string string = re.sub(r'[a-zA-Z!\[\|]', '', date) return string, date_flags ##### Main Function ##### def pdf_extract_gr(image): extractimg = preprocess_image(image) #extractimg = np.array(image) # deskew the image deskewed_image, angle, skew_confidence = deskew(extractimg, model_deskew) # prepare the image for the autoencoder cleanimg = prepare_image_to_autoencoder(deskewed_image) # clean the image img = autoencode_ONNX(cleanimg, model_denoise) # extract the entries from the image df = extract_detected_entries_pdl(img) # first name firstnamerow = df.iloc[0] firstname = firstnamerow[0] firstnameconfidence = round(float(firstnamerow[1]) * 100,3) firstnameconfidence = f"{firstnameconfidence}%" # surname surnamerow = df.iloc[1] surname = surnamerow[0] surnameconfidence = round(float(surnamerow[1]) * 100,3) surnameconfidence = f"{surnameconfidence}%" # death date condifence dodrow = df.iloc[2] dodname = dodrow[0] dodconfidence = round(float(dodrow[1]) * 100,3) dodconfidence = f"{dodconfidence}%" # return all the results return df, deskewed_image, angle, skew_confidence, img, firstname, firstnameconfidence, surname, surnameconfidence, dodname, dodconfidence ##### Gradio Style ##### css = """ .run_container { display: flex; flex-direction: column; align-items: center; gap: 10px; } .run_btn { margin: auto; width: 50%; display: flex; } .upload_cell { margin: auto; display: flex; } .results_container { display: flex; justify-content: space-evenly; } .results_cell { } """ ##### Gradio Blocks ##### with gr.Blocks(css = css) as demo: gr.Markdown(""" # Death Certificate Extraction """, elem_classes = "h1") gr.Markdown("Upload a PDF, extract data") with gr.Box(elem_classes = "run_container"): # ExtractInput = gr.File(label = "Death Certificate", elem_classes="upload_cell") ExtractButton = gr.Button(label = "Extract", elem_classes="run_btn") with gr.Row(elem_id = "hide"): with gr.Column(): ExtractInput = gr.Image() with gr.Column(): # ExtractResult = gr.Image(label = "result") with gr.Row(elem_classes = "results_container"): FirstNameBox = gr.Textbox(label = "First Name", elem_classes = "results_cell") FirstNameConfidenceBox = gr.Textbox(label = "First Name Confidence", elem_classes = "results_cell") with gr.Row(elem_classes = "results_container"): SurnameNameBox = gr.Textbox(label = "Surname", elem_classes = "results_cell") SurnameNameConfidenceBox = gr.Textbox(label = "Surname Confidence", elem_classes = "results_cell") with gr.Row(elem_classes = "results_container"): DODBox = gr.Textbox(label = "Date of Death", elem_classes = "results_cell") DODConfidenceBox = gr.Textbox(label = "Date of Death Confidence", elem_classes = "results_cell") with gr.Accordion("Full Results", open = False): ExtractDF = gr.Dataframe(label = "Results") with gr.Accordion("Clean Image", open = False): CleanOutput = gr.Image() with gr.Accordion("Deskew", open = False): DeskewOutput = gr.Image() with gr.Column(): DeskewAngle = gr.Number(label = "Angle") with gr.Column(): DeskewConfidence = gr.Number(label = "Confidence") ExtractButton.click(fn=pdf_extract_gr, inputs = ExtractInput, outputs = [ExtractDF, DeskewOutput, DeskewAngle, DeskewConfidence, CleanOutput, FirstNameBox, FirstNameConfidenceBox, SurnameNameBox, SurnameNameConfidenceBox, DODBox, DODConfidenceBox]) demo.launch(show_api=True, share=False, debug=True)