Spaces:
Sleeping
Sleeping
Added App Folder
Browse files- app/__init__.py +16 -0
- app/api.py +16 -0
- app/routes/adhaarApi.py +70 -0
- app/routes/panApi.py +70 -0
- app/services/adhaarServices/adhaarDataExtractor.py +59 -0
- app/services/adhaarServices/ocr.py +44 -0
- app/services/panServices/panDataExtractor.py +61 -0
- app/services/panServices/panOcr.py +52 -0
- app/utils/1.png +0 -0
- app/utils/2.png +0 -0
- app/utils/3.png +0 -0
- app/utils/4.png +0 -0
- app/utils/azureOCR.py +42 -0
- app/utils/imageUtils.py +51 -0
- app/utils/seprator3.png +0 -0
app/__init__.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask
|
2 |
+
from ultralytics import YOLO
|
3 |
+
|
4 |
+
def create_app():
|
5 |
+
app = Flask(__name__)
|
6 |
+
from .api import ocr_bp
|
7 |
+
app.register_blueprint(ocr_bp)
|
8 |
+
|
9 |
+
with app.app_context():
|
10 |
+
# Load model once
|
11 |
+
app.models = {
|
12 |
+
'adhaarModel': YOLO('models/aadhaarYolov8.pt'),
|
13 |
+
'panModel': YOLO('models/PanYolov8.pt') # Load additional models as needed
|
14 |
+
}
|
15 |
+
|
16 |
+
return app
|
app/api.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Blueprint
|
2 |
+
import requests
|
3 |
+
import os
|
4 |
+
from .routes.adhaarApi import ocrAdhaar
|
5 |
+
from .routes.panApi import ocrPan
|
6 |
+
ocr_bp = Blueprint('ocr', __name__)
|
7 |
+
session = requests.Session()
|
8 |
+
mode = os.getenv("PROJECT_MODE")
|
9 |
+
|
10 |
+
@ocr_bp.route('/ocrPan', methods=['POST'])
|
11 |
+
def getResponse_Pan():
|
12 |
+
return ocrPan(mode, session)
|
13 |
+
|
14 |
+
@ocr_bp.route('/ocrAdhaar', methods=['POST'])
|
15 |
+
def getResponse_Adhaar():
|
16 |
+
return ocrAdhaar(mode, session)
|
app/routes/adhaarApi.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from flask import request, jsonify, current_app
|
3 |
+
from PIL import Image, UnidentifiedImageError
|
4 |
+
from io import BytesIO
|
5 |
+
import base64
|
6 |
+
import requests
|
7 |
+
from ..services.adhaarServices.ocr import process_results
|
8 |
+
import io
|
9 |
+
def ocrAdhaar(mode, session):
|
10 |
+
try:
|
11 |
+
print("API HIT ************* AADHAAROCR")
|
12 |
+
data = request.get_json()
|
13 |
+
if not data:
|
14 |
+
return jsonify({"error": "Invalid request payload"}), 400
|
15 |
+
|
16 |
+
if mode == "prod":
|
17 |
+
if not data.get('image'):
|
18 |
+
return jsonify({"error": "Image data/buffer is required"}), 400
|
19 |
+
|
20 |
+
#removing 'data:image/png,base64 ' from buffer.
|
21 |
+
imgBuffer = data.get('image')
|
22 |
+
imgBuffer = re.sub("^data:image/.+;base64,", "", imgBuffer)
|
23 |
+
# Adjust base64 string padding
|
24 |
+
if len(imgBuffer) % 4:
|
25 |
+
imgBuffer += '=' * (4 - len(imgBuffer) % 4)
|
26 |
+
|
27 |
+
try:
|
28 |
+
img_data = base64.b64decode(imgBuffer)
|
29 |
+
img = Image.open(BytesIO(img_data))
|
30 |
+
img.verify() # Verify image format
|
31 |
+
img = Image.open(io.BytesIO(img_data)) # Re-open image after verification
|
32 |
+
except (base64.binascii.Error, ValueError) as decode_err:
|
33 |
+
return jsonify({"error": f"Image decoding failed: {str(decode_err)}"}), 400
|
34 |
+
except UnidentifiedImageError:
|
35 |
+
return jsonify({"error": "Unable to identify image format."}), 400
|
36 |
+
|
37 |
+
elif mode == "dev":
|
38 |
+
if not data.get('imgUrl'):
|
39 |
+
return jsonify({"error": "Image URL is required"}), 400
|
40 |
+
|
41 |
+
img_url = data.get('imgUrl')
|
42 |
+
response = session.get(img_url)
|
43 |
+
response.raise_for_status()
|
44 |
+
img = Image.open(BytesIO(response.content))
|
45 |
+
img.verify() # Verify image format
|
46 |
+
img = Image.open(BytesIO(response.content)) # Re-open image after verification
|
47 |
+
|
48 |
+
else:
|
49 |
+
return jsonify({"error": "Invalid mode configuration"}), 500
|
50 |
+
|
51 |
+
# Check image format
|
52 |
+
if img.format not in ['JPEG', 'JPG', 'PNG']:
|
53 |
+
return jsonify({"error": "Invalid image format. Only JPG and PNG are supported."}), 400
|
54 |
+
|
55 |
+
# Run detection
|
56 |
+
model = current_app.models.get('adhaarModel')
|
57 |
+
results = model.predict(source=img, save=False)
|
58 |
+
extracted_data = process_results(results, img)
|
59 |
+
|
60 |
+
if extracted_data.get('statusCode') == 400:
|
61 |
+
return jsonify(extracted_data), 400
|
62 |
+
|
63 |
+
return jsonify(extracted_data), 200
|
64 |
+
except requests.RequestException as req_err:
|
65 |
+
return jsonify({"error": f"Image download failed: {str(req_err)}"}), 500
|
66 |
+
except UnidentifiedImageError:
|
67 |
+
return jsonify({"error": "Unable to identify image format."}), 400
|
68 |
+
except Exception as e:
|
69 |
+
current_app.logger.error(f"Unexpected error: {str(e)}")
|
70 |
+
return jsonify({"error": "An unexpected error occurred."}), 500
|
app/routes/panApi.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from flask import request, jsonify, current_app
|
3 |
+
from PIL import Image, UnidentifiedImageError
|
4 |
+
from io import BytesIO
|
5 |
+
import base64
|
6 |
+
import requests
|
7 |
+
from ..services.panServices.panOcr import process_results
|
8 |
+
import io
|
9 |
+
def ocrPan(mode, session):
|
10 |
+
try:
|
11 |
+
print("API HIT ************* PANOCR")
|
12 |
+
data = request.get_json()
|
13 |
+
if not data:
|
14 |
+
return jsonify({"error": "Invalid request payload"}), 400
|
15 |
+
|
16 |
+
if mode == "prod":
|
17 |
+
if not data.get('image'):
|
18 |
+
return jsonify({"error": "Image data/buffer is required"}), 400
|
19 |
+
|
20 |
+
#removing 'data:image/png,base64 ' from buffer.
|
21 |
+
imgBuffer = data.get('image')
|
22 |
+
imgBuffer = re.sub("^data:image/.+;base64,", "", imgBuffer)
|
23 |
+
# Adjust base64 string padding
|
24 |
+
if len(imgBuffer) % 4:
|
25 |
+
imgBuffer += '=' * (4 - len(imgBuffer) % 4)
|
26 |
+
|
27 |
+
try:
|
28 |
+
img_data = base64.b64decode(imgBuffer)
|
29 |
+
img = Image.open(BytesIO(img_data))
|
30 |
+
img.verify() # Verify image format
|
31 |
+
img = Image.open(io.BytesIO(img_data)) # Re-open image after verification
|
32 |
+
except (base64.binascii.Error, ValueError) as decode_err:
|
33 |
+
return jsonify({"error": f"Image decoding failed: {str(decode_err)}"}), 400
|
34 |
+
except UnidentifiedImageError:
|
35 |
+
return jsonify({"error": "Unable to identify image format."}), 400
|
36 |
+
|
37 |
+
elif mode == "dev":
|
38 |
+
if not data.get('imgUrl'):
|
39 |
+
return jsonify({"error": "Image URL is required"}), 400
|
40 |
+
|
41 |
+
img_url = data.get('imgUrl')
|
42 |
+
response = session.get(img_url)
|
43 |
+
response.raise_for_status()
|
44 |
+
img = Image.open(BytesIO(response.content))
|
45 |
+
img.verify() # Verify image format
|
46 |
+
img = Image.open(BytesIO(response.content)) # Re-open image after verification
|
47 |
+
|
48 |
+
else:
|
49 |
+
return jsonify({"error": "Invalid mode configuration"}), 500
|
50 |
+
|
51 |
+
# Check image format
|
52 |
+
if img.format not in ['JPEG', 'JPG', 'PNG']:
|
53 |
+
return jsonify({"error": "Invalid image format. Only JPG and PNG are supported."}), 400
|
54 |
+
|
55 |
+
# Run detection
|
56 |
+
model = current_app.models.get('panModel')
|
57 |
+
results = model.predict(source=img, save=False)
|
58 |
+
extracted_data = process_results(results, img)
|
59 |
+
|
60 |
+
if extracted_data.get('statusCode') == 400:
|
61 |
+
return jsonify(extracted_data), 400
|
62 |
+
|
63 |
+
return jsonify(extracted_data), 200
|
64 |
+
except requests.RequestException as req_err:
|
65 |
+
return jsonify({"error": f"Image download failed: {str(req_err)}"}), 500
|
66 |
+
except UnidentifiedImageError:
|
67 |
+
return jsonify({"error": "Unable to identify image format."}), 400
|
68 |
+
except Exception as e:
|
69 |
+
current_app.logger.error(f"Unexpected error: {str(e)}")
|
70 |
+
return jsonify({"error": "An unexpected error occurred."}), 500
|
app/services/adhaarServices/adhaarDataExtractor.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import datetime
|
2 |
+
|
3 |
+
def extract_details(texts):
|
4 |
+
details = {'name': '', 'gender': '', 'dob': '', 'aadhaarNo': ''}
|
5 |
+
|
6 |
+
current_year = datetime.datetime.now().year
|
7 |
+
|
8 |
+
for text in texts:
|
9 |
+
# Check if colon exists in text and split accordingly
|
10 |
+
if ':' in text:
|
11 |
+
text = text.split(':')[1].strip()
|
12 |
+
|
13 |
+
cleaned_text = text.replace(':', '').strip()
|
14 |
+
|
15 |
+
# Remove leading non-alphabetic characters for gender detection and strip spaces
|
16 |
+
cleaned_gender = cleaned_text.lstrip('.-/').strip()
|
17 |
+
|
18 |
+
# Check if the text is the name (only alphabets, spaces, and possibly dots)
|
19 |
+
if (all(char.isalpha() or char.isspace() or char == '.' for char in cleaned_text)
|
20 |
+
and cleaned_gender.lower() not in ['male', 'female']):
|
21 |
+
details['name'] = cleaned_text
|
22 |
+
|
23 |
+
# Check if the text is the DOB (format: dd/mm/yyyy or yyyy)
|
24 |
+
elif (len(cleaned_text) == 4 and
|
25 |
+
cleaned_text.isdigit() and
|
26 |
+
1900 < int(cleaned_text) < current_year):
|
27 |
+
details['dob'] = cleaned_text
|
28 |
+
|
29 |
+
# Check if the text is the DOB (format: dd/mm/yyyy or dd-mm-yyyy)
|
30 |
+
elif (len(cleaned_text) == 10 and
|
31 |
+
(cleaned_text[2] in ['/', '-']) and
|
32 |
+
(cleaned_text[5] in ['/', '-']) and
|
33 |
+
cleaned_text.replace('/', '').replace('-', '').isdigit()):
|
34 |
+
details['dob'] = cleaned_text
|
35 |
+
|
36 |
+
# Check if the text is the gender (either 'Male' or 'Female')
|
37 |
+
elif cleaned_gender.lower() in ['male', 'female']:
|
38 |
+
details['gender'] = cleaned_gender.capitalize()
|
39 |
+
|
40 |
+
# Check if the text is the Aadhaar number (12 digits after removing spaces)
|
41 |
+
elif cleaned_text.replace(' ', '').isdigit() and len(cleaned_text.replace(' ', '')) == 12:
|
42 |
+
details['aadhaarNo'] = cleaned_text
|
43 |
+
|
44 |
+
# Check if any key's value is empty
|
45 |
+
if any(value == '' for value in details.values()):
|
46 |
+
error_key = next(key for key, value in details.items() if value == '')
|
47 |
+
result = {
|
48 |
+
'statusCode': 400,
|
49 |
+
'result': details,
|
50 |
+
'error': f'{error_key} value is not found due to bad image.'
|
51 |
+
}
|
52 |
+
else:
|
53 |
+
result = {
|
54 |
+
'statusCode': 200,
|
55 |
+
'result': details,
|
56 |
+
'error': ''
|
57 |
+
}
|
58 |
+
|
59 |
+
return result
|
app/services/adhaarServices/ocr.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from io import BytesIO
|
2 |
+
from ...utils.azureOCR import analyze_image
|
3 |
+
from ...utils.imageUtils import resize_if_needed, all_cropped_images_to_one_image
|
4 |
+
from app.services.adhaarServices.adhaarDataExtractor import extract_details
|
5 |
+
|
6 |
+
def process_results(results, img):
|
7 |
+
precision_data = {label: {"correct": 0, "total": 0} for label in ["aadharNo", "name", "dob", "gender", "address"]}
|
8 |
+
confidence_threshold = 0.3
|
9 |
+
input_image_format = img.format if img.format else "PNG"
|
10 |
+
valid_formats = ["JPEG", "PNG", "BMP", "GIF", "TIFF"]
|
11 |
+
input_image_format = input_image_format if input_image_format in valid_formats else "PNG"
|
12 |
+
|
13 |
+
label_to_image = {}
|
14 |
+
extracted_data = {"adhaarNo": "", "dob": "", "gender": "", "name": "", "address": ""}
|
15 |
+
for result in results:
|
16 |
+
for bbox, cls, conf in zip(result.boxes.xyxy, result.boxes.cls, result.boxes.conf):
|
17 |
+
label = ["aadharNo", "dob", "gender", "name", "address"][int(cls)]
|
18 |
+
print(label, conf)
|
19 |
+
if conf < confidence_threshold or label == "address":
|
20 |
+
continue
|
21 |
+
|
22 |
+
x1, y1, x2, y2 = map(int, bbox.tolist())
|
23 |
+
crop_img = img.crop((x1, y1, x2, y2))
|
24 |
+
crop_img = resize_if_needed(crop_img)
|
25 |
+
|
26 |
+
if label not in label_to_image or label_to_image[label][1] < conf:
|
27 |
+
label_to_image[label] = (crop_img, conf)
|
28 |
+
precision_data[label]["total"] += 1
|
29 |
+
precision_data[label]["correct"] += 1 # Replace with actual OCR validation check
|
30 |
+
|
31 |
+
cropped_images = [img for label, (img, conf) in sorted(label_to_image.items()) if label != "address"]
|
32 |
+
final_image = all_cropped_images_to_one_image(cropped_images, separator_image_path='app/utils/seprator3.png')
|
33 |
+
|
34 |
+
buffer = BytesIO()
|
35 |
+
final_image.save(buffer, format=input_image_format)
|
36 |
+
buffer.seek(0)
|
37 |
+
|
38 |
+
response = analyze_image(buffer.getvalue(), input_image_format)
|
39 |
+
# print(response)
|
40 |
+
lines = response['readResult']['blocks'][0]['lines']
|
41 |
+
texts = [line['text'] for line in lines]
|
42 |
+
print(texts)
|
43 |
+
extracted_data = extract_details(texts)
|
44 |
+
return extracted_data
|
app/services/panServices/panDataExtractor.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
def extract_panData(data):
|
4 |
+
unwanted_words = ["Name","/Name", "Date of Birth", "/Date of Birth", "Permanent Account Number", "Father's Name", "14 /Name", "/Father's Name"]
|
5 |
+
|
6 |
+
# Clean the array by removing unwanted words and invalid entries
|
7 |
+
cleaned_data = []
|
8 |
+
combination_pattern = re.compile(r'(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*(),?":{}|<>])')
|
9 |
+
|
10 |
+
for item in data:
|
11 |
+
if item not in unwanted_words and not combination_pattern.search(item):
|
12 |
+
cleaned_data.append(item)
|
13 |
+
|
14 |
+
# Initialize result object
|
15 |
+
result = {
|
16 |
+
"statusCode": 200,
|
17 |
+
"error": '',
|
18 |
+
"data": {
|
19 |
+
"panNo": '',
|
20 |
+
"name": '',
|
21 |
+
"fatherName": '',
|
22 |
+
"dob": ''
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
# Check and extract PAN number
|
27 |
+
print(cleaned_data)
|
28 |
+
pan_pattern = re.compile(r'^[A-Z]{5}[0-9]{4}[A-Z]$')
|
29 |
+
if len(cleaned_data) > 0 and pan_pattern.match(cleaned_data[3]):
|
30 |
+
result["data"]["panNo"] = cleaned_data[3]
|
31 |
+
else:
|
32 |
+
result["data"]["panNo"] = ''
|
33 |
+
|
34 |
+
# Check and extract name
|
35 |
+
name_pattern = re.compile(r'^[A-Za-z .]+$')
|
36 |
+
if len(cleaned_data) > 1 and name_pattern.match(cleaned_data[2]):
|
37 |
+
result["data"]["name"] = cleaned_data[2]
|
38 |
+
else:
|
39 |
+
result["data"]["name"] = ''
|
40 |
+
|
41 |
+
# Check and extract father's name
|
42 |
+
if len(cleaned_data) > 2 and name_pattern.match(cleaned_data[1]):
|
43 |
+
result["data"]["fatherName"] = cleaned_data[1]
|
44 |
+
else:
|
45 |
+
result["data"]["fatherName"] = ''
|
46 |
+
|
47 |
+
# Check and extract date of birth
|
48 |
+
dob_pattern = re.compile(r'^\d{2}[-/]\d{2}[-/]\d{4}$')
|
49 |
+
if len(cleaned_data) > 3 and dob_pattern.match(cleaned_data[0]):
|
50 |
+
result["data"]["dob"] = cleaned_data[0]
|
51 |
+
else:
|
52 |
+
result["data"]["dob"] = ''
|
53 |
+
|
54 |
+
# Check if any value is empty and set error message
|
55 |
+
for key, value in result["data"].items():
|
56 |
+
if value == '':
|
57 |
+
result["statusCode"] = 400
|
58 |
+
result["error"] = f"{key} value is not found due to bad image."
|
59 |
+
break
|
60 |
+
|
61 |
+
return result
|
app/services/panServices/panOcr.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from io import BytesIO
|
2 |
+
from ...utils.azureOCR import analyze_image
|
3 |
+
from ...utils.imageUtils import resize_if_needed, all_cropped_images_to_one_image
|
4 |
+
from .panDataExtractor import extract_panData
|
5 |
+
|
6 |
+
def process_results(results, img):
|
7 |
+
label_indices = {"dob": 0, "father": 1, "name": 2, "pan_num": 3}
|
8 |
+
confidence_threshold = 0.3
|
9 |
+
input_image_format = img.format if img.format else "PNG"
|
10 |
+
valid_formats = ["JPEG", "PNG", "BMP", "GIF", "TIFF"]
|
11 |
+
input_image_format = input_image_format if input_image_format in valid_formats else "PNG"
|
12 |
+
|
13 |
+
cropped_images_with_labels = []
|
14 |
+
precision_data = {label: {"correct": 0, "total": 0} for label in label_indices.keys()}
|
15 |
+
# extracted_data = {"pan_num": "", "name": "", "father": "", "dob": ""}
|
16 |
+
|
17 |
+
for result in results:
|
18 |
+
for bbox, cls, conf in zip(result.boxes.xyxy, result.boxes.cls, result.boxes.conf):
|
19 |
+
label = ["dob", "father", "name", "pan_num"][int(cls)]
|
20 |
+
print(label, conf)
|
21 |
+
if conf < confidence_threshold:
|
22 |
+
continue
|
23 |
+
|
24 |
+
x1, y1, x2, y2 = map(int, bbox.tolist())
|
25 |
+
crop_img = img.crop((x1, y1, x2, y2))
|
26 |
+
crop_img = resize_if_needed(crop_img)
|
27 |
+
crop_img.save(f"temp_{label}.png")
|
28 |
+
cropped_images_with_labels.append((crop_img, label_indices[label], conf))
|
29 |
+
precision_data[label]["total"] += 1
|
30 |
+
precision_data[label]["correct"] += 1 # Replace with actual OCR validation check
|
31 |
+
|
32 |
+
# Sort the images by their label indices in ascending order
|
33 |
+
cropped_images_with_labels.sort(key=lambda x: x[1])
|
34 |
+
print(cropped_images_with_labels, "cropped images with labels")
|
35 |
+
# Extract only the images for concatenation
|
36 |
+
cropped_images = [img for img, _, _ in cropped_images_with_labels]
|
37 |
+
# print(cropped_images, "cropped images")
|
38 |
+
if not cropped_images:
|
39 |
+
raise ValueError("No images were cropped.")
|
40 |
+
|
41 |
+
final_image = all_cropped_images_to_one_image(cropped_images, separator_image_path='app/utils/seprator3.png')
|
42 |
+
buffer = BytesIO()
|
43 |
+
final_image.save(buffer, format=input_image_format)
|
44 |
+
buffer.seek(0)
|
45 |
+
|
46 |
+
response = analyze_image(buffer.getvalue(), input_image_format)
|
47 |
+
print(response, "response")
|
48 |
+
lines = response['readResult']['blocks'][0]['lines']
|
49 |
+
texts = [line['text'] for line in lines]
|
50 |
+
print(texts, "text after microsoft ocr")
|
51 |
+
extracted_data = extract_panData(texts)
|
52 |
+
return extracted_data
|
app/utils/1.png
ADDED
![]() |
app/utils/2.png
ADDED
![]() |
app/utils/3.png
ADDED
![]() |
app/utils/4.png
ADDED
![]() |
app/utils/azureOCR.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
|
3 |
+
def analyze_image(image_buffer, imgType):
|
4 |
+
url = 'https://portal.vision.cognitive.azure.com/api/demo/analyze?features=read'
|
5 |
+
headers = {
|
6 |
+
'Accept': '*/*',
|
7 |
+
'Accept-Language': 'en-GB,en;q=0.9,en-US;q=0.8',
|
8 |
+
'Connection': 'keep-alive',
|
9 |
+
'Cookie': 'ARRAffinity=ecee788152e01b1e3805dc3134d03cab01ed97f61e69beeb53fcc885e5d0e707; ARRAffinitySameSite=ecee788152e01b1e3805dc3134d03cab01ed97f61e69beeb53fcc885e5d0e707; ai_user=Hps6Ce/6zz/k9q5icFxuiU|2024-07-03T12:30:25.655Z; MicrosoftApplicationsTelemetryDeviceId=89ed4fb7-9ae7-4e71-af1e-add358e41b9e; MSFPC=GUID=cbf8cbf9ba1b48c69374f48574f06958&HASH=cbf8&LV=202407&V=4&LU=1720009794093; ai_session=ZABOsZ0VsIaPiorOXIr2B+|1720009825676|1720010022866',
|
10 |
+
'DNT': '1',
|
11 |
+
'Origin': 'https://portal.vision.cognitive.azure.com',
|
12 |
+
'Referer': 'https://portal.vision.cognitive.azure.com/demo/extract-text-from-images',
|
13 |
+
'Sec-Fetch-Dest': 'empty',
|
14 |
+
'Sec-Fetch-Mode': 'cors',
|
15 |
+
'Sec-Fetch-Site': 'same-origin',
|
16 |
+
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36 Edg/125.0.0.0',
|
17 |
+
'api-call-origin': 'Microsoft.Cognitive.CustomVision.Portal',
|
18 |
+
'content-disposition': 'form-data; name=Foo',
|
19 |
+
'enctype': 'multipart/form-data',
|
20 |
+
'request-context': 'appId=cid-v1:e2fac58d-316d-4cf3-a225-11d998b143db',
|
21 |
+
'request-id': '|37e377d2a90c4c1ba78805dc901a47ac.fb0593645bfd456f',
|
22 |
+
'sec-ch-ua': '"Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
|
23 |
+
'sec-ch-ua-mobile': '?1',
|
24 |
+
'sec-ch-ua-platform': '"Android"',
|
25 |
+
'traceparent': '00-37e377d2a90c4c1ba78805dc901a47ac-fb0593645bfd456f-01'
|
26 |
+
}
|
27 |
+
|
28 |
+
files = {'file': ( f"allimage.{imgType}", image_buffer, f"image/{imgType}")}
|
29 |
+
response = requests.post(url, headers=headers, files=files)
|
30 |
+
if response.status_code != 200:
|
31 |
+
raise Exception(f"Failed to analyze image: {response.status_code} - {response.text}")
|
32 |
+
else:
|
33 |
+
return response.json()
|
34 |
+
|
35 |
+
# Example usage
|
36 |
+
|
37 |
+
|
38 |
+
# Example usage
|
39 |
+
# file_path = '/path/to/your/image.png'
|
40 |
+
# url = 'https://portal.vision.cognitive.azure.com/api/demo/analyze?features=read'
|
41 |
+
# response = analyze_image(file_path, url)
|
42 |
+
# print(response)
|
app/utils/imageUtils.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
def resize_if_needed(image):
|
6 |
+
width, height = image.size
|
7 |
+
if width < 50 or height < 50:
|
8 |
+
scale = max(70 / width, 70 / height)
|
9 |
+
elif width > 16000 or height > 16000:
|
10 |
+
scale = min(16000 / width, 16000 / height)
|
11 |
+
else:
|
12 |
+
return image
|
13 |
+
return image.resize((int(width * scale), int(height * scale)), Image.LANCZOS)
|
14 |
+
|
15 |
+
def center_on_white_background(image):
|
16 |
+
width, height = image.size
|
17 |
+
if width < 150 and height < 50:
|
18 |
+
background = Image.new('RGB', (150, 150), (255, 255, 255))
|
19 |
+
x, y = (150 - width) // 2, (150 - height) // 2
|
20 |
+
background.paste(image, (x, y))
|
21 |
+
return background
|
22 |
+
return image
|
23 |
+
|
24 |
+
def all_cropped_images_to_one_image(cropped_images, separator_image_path):
|
25 |
+
separator_image = Image.open(separator_image_path)
|
26 |
+
separator_height = separator_image.height
|
27 |
+
|
28 |
+
total_height = sum(img.height for img in cropped_images) + (len(cropped_images) - 1) * separator_height
|
29 |
+
print("Total Height:", total_height)
|
30 |
+
|
31 |
+
max_width = max(max(img.width for img in cropped_images), separator_image.width)
|
32 |
+
print("Max Width:", max_width)
|
33 |
+
|
34 |
+
combined_image = Image.new('RGB', (max_width, total_height))
|
35 |
+
y = 0
|
36 |
+
print(cropped_images, "before combine")
|
37 |
+
|
38 |
+
for i, img in enumerate(cropped_images):
|
39 |
+
combined_image.paste(img, (0, y))
|
40 |
+
y += img.height
|
41 |
+
if i < len(cropped_images) - 1:
|
42 |
+
combined_image.paste(separator_image, (0, y))
|
43 |
+
y += separator_height
|
44 |
+
|
45 |
+
combined_image.save("combinedImage.png")
|
46 |
+
return combined_image
|
47 |
+
|
48 |
+
def preprocess_image(image):
|
49 |
+
gray = cv2.cvtColor(np.array(image), cv2.COLOR_BGR2GRAY)
|
50 |
+
_, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
51 |
+
return Image.fromarray(binary)
|
app/utils/seprator3.png
ADDED
![]() |