|
import cv2 |
|
import numpy as np |
|
from PIL import Image |
|
import gradio as gr |
|
from paddleocr import PaddleOCR |
|
import datetime |
|
import logging |
|
import sys |
|
from simple_salesforce import Salesforce |
|
import time |
|
import traceback |
|
import re |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(levelname)s - %(message)s', |
|
handlers=[logging.FileHandler('app_log.txt'), logging.StreamHandler(sys.stdout)] |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
SALESFORCE_USERNAME = "kushal@sathkrutha.com" |
|
SALESFORCE_PASSWORD = "Suri@777" |
|
SALESFORCE_SECURITY_TOKEN = "ME1oo3rEg3YykYIDlYc4ZPBO" |
|
|
|
def connect_salesforce(): |
|
try: |
|
logger.info("Connecting to Salesforce...") |
|
sf = Salesforce( |
|
username=SALESFORCE_USERNAME, |
|
password=SALESFORCE_PASSWORD, |
|
security_token=SALESFORCE_SECURITY_TOKEN |
|
) |
|
logger.info("Connected to Salesforce.") |
|
return sf |
|
except Exception as e: |
|
logger.error(f"Salesforce connection error: {str(e)}") |
|
raise |
|
|
|
sf = connect_salesforce() |
|
|
|
def check_existing_blueprint_title(blueprint_title): |
|
try: |
|
query = f"SELECT Id FROM Blue_print_Estimation__c WHERE Blueprint_Title__c = '{blueprint_title}'" |
|
existing_records = sf.query(query) |
|
return existing_records['totalSize'] > 0 |
|
except Exception as e: |
|
logger.error(f"Error querying Salesforce: {str(e)}") |
|
raise |
|
|
|
def generate_unique_blueprint_title(blueprint_title): |
|
return f"{blueprint_title}_{int(time.time())}" |
|
|
|
def friendly_field_names(sf_record): |
|
mapping = { |
|
"Blueprint_Title__c": "Blueprint Title", |
|
"Uploaded_By__c": "Uploaded By", |
|
"Upload_Date__c": "Upload Date", |
|
"Structure_Type__c": "Structure Type", |
|
"Cement_Required_KG__c": "Cement Required (kg)", |
|
"Steel_Required_KG__c": "Steel Required (kg)", |
|
"Brick_Count__c": "Brick Count", |
|
"Sand_Required_CFT__c": "Sand (CFT)", |
|
"Material_Summary__c": "Material Summary", |
|
"Gravel__c": "Gravel (CFT)" |
|
} |
|
bullet_points = [] |
|
for key, value in sf_record.items(): |
|
display_key = mapping.get(key, key) |
|
if key != "Material_Summary__c": |
|
bullet_points.append(f"- {display_key}: {value}") |
|
return "\n".join(bullet_points) |
|
|
|
try: |
|
logger.info("Initializing PaddleOCR...") |
|
ocr = PaddleOCR(use_angle_cls=True, lang='en') |
|
logger.info("PaddleOCR initialized.") |
|
except Exception as e: |
|
logger.error(f"OCR init error: {str(e)}") |
|
raise |
|
|
|
def calculate_materials_based_on_dimensions(total_wall_length_m, wall_height_ft, slab_count, total_slab_area_sqft, slab_thickness_ft, blueprint_area_sqft): |
|
|
|
total_wall_length_ft = total_wall_length_m * 3.28084 |
|
wall_area_sqft = total_wall_length_ft * wall_height_ft |
|
|
|
|
|
slab_area_per_slab = total_slab_area_sqft / slab_count |
|
slab_volume_cuft = total_slab_area_sqft * slab_thickness_ft |
|
|
|
|
|
cement_ratio = 1 |
|
sand_ratio = 2 |
|
gravel_ratio = 4 |
|
total_ratio = cement_ratio + sand_ratio + gravel_ratio |
|
|
|
|
|
wall_cement_ratio = 1 |
|
wall_sand_ratio = 3 |
|
wall_gravel_ratio = 1 |
|
wall_total_ratio = wall_cement_ratio + wall_sand_ratio + wall_gravel_ratio |
|
|
|
|
|
cement_kg_per_cuft = 42.6 |
|
steel_per_cuft = 1.5 |
|
brick_size_sqft = 0.10 |
|
mortar_cuft_per_sqft = 0.02 |
|
|
|
|
|
mortar_volume_cuft = wall_area_sqft * mortar_cuft_per_sqft |
|
cement_wall_cuft = (wall_cement_ratio / wall_total_ratio) * mortar_volume_cuft |
|
sand_wall_cuft = (wall_sand_ratio / wall_total_ratio) * mortar_volume_cuft |
|
gravel_wall_cuft = (wall_gravel_ratio / wall_total_ratio) * mortar_volume_cuft |
|
cement_wall_kg = cement_wall_cuft * cement_kg_per_cuft |
|
steel_wall_kg = wall_area_sqft * 0.1 |
|
bricks_wall = wall_area_sqft / brick_size_sqft |
|
|
|
|
|
cement_slab_cuft = (cement_ratio / total_ratio) * slab_volume_cuft |
|
sand_slab_cuft = (sand_ratio / total_ratio) * slab_volume_cuft |
|
gravel_slab_cuft = (gravel_ratio / total_ratio) * slab_volume_cuft |
|
steel_slab_kg = steel_per_cuft * slab_volume_cuft |
|
cement_slab_kg = cement_slab_cuft * cement_kg_per_cuft |
|
|
|
return { |
|
"wall_area_sqft": round(wall_area_sqft), |
|
"slab_volume_cuft": round(slab_volume_cuft), |
|
"cement_wall_kg": round(cement_wall_kg), |
|
"bricks_wall": round(bricks_wall), |
|
"steel_wall_kg": round(steel_wall_kg), |
|
"sand_wall_cuft": round(sand_wall_cuft), |
|
"gravel_wall_cuft": round(gravel_wall_cuft), |
|
"cement_slab_kg": round(cement_slab_kg), |
|
"sand_slab_cuft": round(sand_slab_cuft), |
|
"gravel_slab_cuft": round(gravel_slab_cuft), |
|
"steel_slab_kg": round(steel_slab_kg), |
|
"total_cement_kg": round(cement_wall_kg + cement_slab_kg), |
|
"total_bricks": round(bricks_wall), |
|
"total_steel_kg": round(steel_wall_kg + steel_slab_kg), |
|
"total_sand_cuft": round(sand_wall_cuft + sand_slab_cuft), |
|
"total_gravel_cuft": round(gravel_wall_cuft + gravel_slab_cuft) |
|
} |
|
|
|
def extract_text_with_paddleocr(image): |
|
try: |
|
open_cv_image = np.array(image) |
|
open_cv_image = cv2.cvtColor(open_cv_image, cv2.COLOR_RGB2BGR) |
|
ocr_results = ocr.ocr(open_cv_image, cls=True) |
|
if ocr_results and ocr_results[0]: |
|
return '\n'.join([line[1][0] for line in ocr_results[0]]) |
|
return "No text detected." |
|
except Exception as e: |
|
logger.error(f"OCR error: {str(e)}") |
|
return f"OCR failed: {str(e)}" |
|
|
|
def clean_extracted_text(text): |
|
lines = text.split('\n') |
|
filtered_lines = [] |
|
for line in lines: |
|
lower_line = line.lower() |
|
if ("drawings are for guidance only" in lower_line or |
|
"plan produced using" in lower_line or |
|
"www." in lower_line or |
|
"http" in lower_line or |
|
"plan has been drawn for illustrative and identification purposes only" in lower_line): |
|
continue |
|
filtered_lines.append(line) |
|
return '\n'.join(filtered_lines) |
|
|
|
def extract_blueprint_area(text): |
|
match = re.search(r"TOTAL AREA:.*?(\d+\.\d+)\s*sq\.\s*feet", text, re.IGNORECASE) |
|
if match: |
|
return float(match.group(1)) |
|
match = re.search(r"TOTAL AREA:.*?(\d+\.\d+)\s*sq\.\s*metres", text, re.IGNORECASE) |
|
if match: |
|
sq_meters = float(match.group(1)) |
|
return sq_meters * 10.7639 |
|
return None |
|
|
|
def estimate_wall_length_from_area(blueprint_area_sqft): |
|
side_length = np.sqrt(blueprint_area_sqft) |
|
perimeter_ft = 4 * side_length |
|
return perimeter_ft / 3.28084 |
|
|
|
def process_blueprint(image, blueprint_title, uploaded_by, structure_type, |
|
slab_count, total_slab_area_sqft, slab_thickness_ft, wall_height_ft): |
|
try: |
|
if check_existing_blueprint_title(blueprint_title): |
|
blueprint_title = generate_unique_blueprint_title(blueprint_title) |
|
logger.info(f"Duplicate blueprint title detected. New title assigned: {blueprint_title}") |
|
|
|
extracted_text = extract_text_with_paddleocr(image) |
|
cleaned_text = clean_extracted_text(extracted_text) |
|
blueprint_area_sqft = extract_blueprint_area(cleaned_text) |
|
if blueprint_area_sqft is None: |
|
logger.warning("Could not extract blueprint area from text. Using default estimation.") |
|
blueprint_area_sqft = total_slab_area_sqft / slab_count |
|
|
|
if abs(blueprint_area_sqft * slab_count - total_slab_area_sqft) > total_slab_area_sqft * 0.5: |
|
logger.warning(f"Blueprint area ({blueprint_area_sqft} sq.ft) and total slab area ({total_slab_area_sqft} sq.ft) mismatch significantly.") |
|
|
|
materials = calculate_materials_based_on_dimensions(blueprint_area_sqft, wall_height_ft, |
|
slab_count, total_slab_area_sqft, slab_thickness_ft, |
|
blueprint_area_sqft) |
|
|
|
|
|
material_summary = f""" |
|
- Construction Material Requirements Summary: |
|
- Total Wall Area: {materials['wall_area_sqft']} sq ft |
|
- Cement (Walls): {materials['cement_wall_kg']} kg |
|
- Bricks (Walls): {materials['bricks_wall']} units |
|
- Steel (Walls): {materials['steel_wall_kg']} kg |
|
- Sand (Walls): {materials['sand_wall_cuft']} CFT |
|
- Gravel (Walls): {materials['gravel_wall_cuft']} CFT |
|
|
|
- Total Slab Volume: {materials['slab_volume_cuft']} cubic ft |
|
- Cement (Slabs): {materials['cement_slab_kg']} kg |
|
- Sand (Slabs): {materials['sand_slab_cuft']} CFT |
|
- Gravel (Slabs): {materials['gravel_slab_cuft']} CFT |
|
- Steel (Slabs): {materials['steel_slab_kg']} kg |
|
|
|
- Total Materials Required: |
|
- Total Cement: {materials['total_cement_kg']} kg |
|
- Total Bricks: {materials['total_bricks']} units |
|
- Total Steel: {materials['total_steel_kg']} kg |
|
- Total Sand: {materials['total_sand_cuft']} CFT |
|
- Total Gravel: {materials['total_gravel_cuft']} CFT |
|
""" |
|
|
|
|
|
material_summary_html = f""" |
|
<ul> |
|
<li><strong>Construction Material Requirements Summary:</strong></li> |
|
<ul> |
|
<li><span style="color: #000000; font-weight: bold;">Total Wall Area:</span> {materials['wall_area_sqft']} sq ft</li> |
|
<li>Cement (Walls): {materials['cement_wall_kg']} kg</li> |
|
<li>Bricks (Walls): {materials['bricks_wall']} units</li> |
|
<li>Steel (Walls): {materials['steel_wall_kg']} kg</li> |
|
<li>Sand (Walls): {materials['sand_wall_cuft']} CFT</li> |
|
<li>Gravel (Walls): {materials['gravel_wall_cuft']} CFT</li> |
|
</ul> |
|
<br> <!-- Adding space between sections --> |
|
|
|
<li><span style="color: #000000; font-weight: bold;">Total Slab Volume:</span> {materials['slab_volume_cuft']} cubic ft</li> |
|
<ul> |
|
<li>Cement (Slabs): {materials['cement_slab_kg']} kg</li> |
|
<li>Sand (Slabs): {materials['sand_slab_cuft']} CFT</li> |
|
<li>Gravel (Slabs): {materials['gravel_slab_cuft']} CFT</li> |
|
<li>Steel (Slabs): {materials['steel_slab_kg']} kg</li> |
|
</ul> |
|
<br> <!-- Adding space between sections --> |
|
<li><strong>Total Materials Required:</strong></li> |
|
<ul> |
|
<li>Total Cement: {materials['total_cement_kg']} kg</li> |
|
<li>Total Bricks: {materials['total_bricks']} units</li> |
|
<li>Total Steel: {materials['total_steel_kg']} kg</li> |
|
<li>Total Sand: {materials['total_sand_cuft']} CFT</li> |
|
<li>Total Gravel: {materials['total_gravel_cuft']} CFT</li> |
|
</ul> |
|
</ul> |
|
""" |
|
|
|
upload_date = datetime.datetime.now().strftime("%Y-%m-%d") |
|
|
|
salesforce_record = { |
|
"Blueprint_Title__c": blueprint_title, |
|
"Uploaded_By__c": uploaded_by, |
|
"Upload_Date__c": upload_date, |
|
"Structure_Type__c": structure_type, |
|
"Cement_Required_KG__c": materials['total_cement_kg'], |
|
"Steel_Required_KG__c": materials['total_steel_kg'], |
|
"Brick_Count__c": materials['total_bricks'], |
|
"Sand_Required_CFT__c": float(materials['total_sand_cuft']), |
|
"Material_Summary__c": material_summary_html, |
|
"Gravel__c": materials['total_gravel_cuft'] |
|
} |
|
|
|
logger.info(f"Salesforce record payload: {salesforce_record}") |
|
|
|
try: |
|
response = sf.Blue_print_Estimation__c.create(salesforce_record) |
|
logger.info(f"Salesforce record created successfully: {response}") |
|
salesforce_record['Salesforce_Record_Id'] = response['id'] |
|
created_record = sf.Blue_print_Estimation__c.get(response['id']) |
|
saved_sand_value = created_record.get('Sand_Required_CFT__c', 'Not Found') |
|
logger.info(f"Saved Sand_Required_CFT__c value in Salesforce record: {saved_sand_value}") |
|
except Exception as e: |
|
tb_str = traceback.format_exc() |
|
logger.error(f"Salesforce insert failed: {str(e)}\nTraceback:\n{tb_str}") |
|
salesforce_record['Salesforce_Error'] = str(e) |
|
return friendly_field_names(salesforce_record), f"Salesforce insert failed: {str(e)}" |
|
|
|
return friendly_field_names(salesforce_record), material_summary |
|
|
|
except Exception as e: |
|
logger.error(f"Error processing blueprint: {str(e)}") |
|
return f"- Error: {str(e)}", None |
|
|
|
def gradio_process_file(image, blueprint_title, uploaded_by, structure_type, |
|
slab_count, total_slab_area_sqft, slab_thickness_ft, wall_height_ft): |
|
if image is None: |
|
return "- Error: No image uploaded.", None |
|
try: |
|
slab_count = int(slab_count) |
|
total_slab_area_sqft = float(total_slab_area_sqft) |
|
slab_thickness_ft = float(slab_thickness_ft) |
|
wall_height_ft = float(wall_height_ft) |
|
|
|
if slab_count <= 0 or total_slab_area_sqft <= 0 or slab_thickness_ft <= 0 or wall_height_ft <= 0: |
|
raise ValueError("All numeric inputs must be positive.") |
|
except Exception as e: |
|
return f"- Error: Invalid inputs: {str(e)}", None |
|
|
|
return process_blueprint(image, blueprint_title, uploaded_by, structure_type, |
|
slab_count, total_slab_area_sqft, slab_thickness_ft, wall_height_ft) |
|
|
|
|
|
with gr.Blocks(css='button:has(span:contains("Share via Link")) { display: none !important; }') as interface: |
|
gr.Markdown("# Blueprint Material Estimator") |
|
gr.Markdown("Upload a blueprint image and enter construction parameters to estimate materials and store the result in Salesforce.") |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(): |
|
image_input = gr.Image(type="pil", label="Upload Blueprint Image", interactive=True) |
|
blueprint_title_input = gr.Textbox(label="Blueprint Title", interactive=True) |
|
uploaded_by_input = gr.Textbox(label="Uploaded By", interactive=True) |
|
structure_type_input = gr.Dropdown(choices=["Residential", "Commercial", "Industrial"], label="Structure Type", interactive=True) |
|
slab_count_input = gr.Number(label="Slab Count", minimum=1, step=1, interactive=True) |
|
total_slab_area_input = gr.Number(label="Total Slab Area (sq ft)", minimum=1.0, interactive=True) |
|
slab_thickness_input = gr.Number(label="Slab Thickness (ft)", minimum=0.1, interactive=True) |
|
wall_height_input = gr.Number(label="Wall Height (ft)", minimum=0.1, interactive=True) |
|
submit_button = gr.Button("Submit") |
|
|
|
|
|
with gr.Column(): |
|
with gr.Row(): |
|
result_output = gr.Textbox(label="Blueprint Analysis Results", lines=20) |
|
extracted_text_output = gr.Textbox(label="Extracted Text from Blueprint", lines=20) |
|
|
|
|
|
submit_button.click( |
|
fn=gradio_process_file, |
|
inputs=[ |
|
image_input, blueprint_title_input, uploaded_by_input, structure_type_input, |
|
slab_count_input, total_slab_area_input, slab_thickness_input, wall_height_input |
|
], |
|
outputs=[result_output, extracted_text_output] |
|
) |
|
|
|
if __name__ == "__main__": |
|
logger.info("Launching app...") |
|
interface.launch() |