dschandra's picture
Update app.py
8219ccf verified
raw
history blame
11.1 kB
import cv2
import numpy as np
import gradio as gr
from pdf2image import convert_from_path
import os
import tempfile
import logging
# Set up logging to debug.log
logging.basicConfig(
filename='debug.log',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Function to validate poppler_path
def validate_poppler_path(poppler_path):
"""
Validate if the provided poppler_path is valid and contains pdftoppm.
Args:
poppler_path (str): Path to poppler binaries.
Returns:
str or None: Valid poppler_path or None if invalid.
"""
if not poppler_path:
return None
if not os.path.isdir(poppler_path):
logger.error("Invalid poppler_path: %s is not a directory", poppler_path)
return None
pdftoppm_path = os.path.join(poppler_path, "pdftoppm" + (".exe" if os.name == "nt" else ""))
if not os.path.isfile(pdftoppm_path):
logger.error("pdftoppm not found in poppler_path: %s", poppler_path)
return None
logger.debug("Valid poppler_path: %s", poppler_path)
return poppler_path
# Function to calculate materials based on blueprint dimensions
def calculate_materials_from_dimensions(wall_area, foundation_area):
"""
Calculate required materials (cement, bricks, steel) based on wall and foundation areas.
Args:
wall_area (float): Wall area in square meters.
foundation_area (float): Foundation area in square meters.
Returns:
dict: Material quantities (cement in kg, bricks in units, steel in kg).
Raises:
ValueError: If areas are negative or invalid.
"""
logger.debug(f"Calculating materials for wall_area={wall_area}, foundation_area={foundation_area}")
# Validate inputs
if not isinstance(wall_area, (int, float)) or not isinstance(foundation_area, (int, float)):
logger.error("Invalid area type: wall_area and foundation_area must be numeric")
raise ValueError("Wall and foundation areas must be numeric")
if wall_area < 0 or foundation_area < 0:
logger.error("Negative areas provided: wall_area=%s, foundation_area=%s", wall_area, foundation_area)
raise ValueError("Wall and foundation areas must be non-negative")
materials = {
"cement": 0,
"bricks": 0,
"steel": 0
}
# Wall calculations (in m²)
if wall_area > 0:
materials['cement'] += wall_area * 10 # 10 kg cement per m² for walls
materials['bricks'] += wall_area * 500 # 500 bricks per m² for walls
materials['steel'] += wall_area * 2 # 2 kg steel per m² for walls
logger.debug("Wall materials: cement=%s kg, bricks=%s, steel=%s kg",
materials['cement'], materials['bricks'], materials['steel'])
# Foundation calculations (in m²)
if foundation_area > 0:
materials['cement'] += foundation_area * 20 # 20 kg cement per m² for foundation
materials['bricks'] += foundation_area * 750 # 750 bricks per m² for foundation
materials['steel'] += foundation_area * 5 # 5 kg steel per m² for foundation
logger.debug("Foundation materials added: cement=%s kg, bricks=%s, steel=%s kg",
materials['cement'], materials['bricks'], materials['steel'])
logger.info("Material calculation complete: %s", materials)
return materials
# Function to process the blueprint from a PDF
def process_blueprint(pdf_path, blueprint_width_m=27, blueprint_height_m=9.78, poppler_path=None):
"""
Process a PDF blueprint to estimate construction materials.
Args:
pdf_path (str): Path to the PDF file.
blueprint_width_m (float): Blueprint width in meters (default: 27).
blueprint_height_m (float): Blueprint height in meters (default: 9.78).
poppler_path (str, optional): Path to poppler binaries.
Returns:
dict: Formatted material estimates or error message.
"""
logger.info("Starting blueprint processing for PDF: %s", pdf_path)
# Validate inputs
if not os.path.exists(pdf_path):
logger.error("PDF file does not exist: %s", pdf_path)
return {"error": f"PDF file not found: {pdf_path}"}
if not isinstance(blueprint_width_m, (int, float)) or not isinstance(blueprint_height_m, (int, float)):
logger.error("Invalid blueprint dimensions: width=%s, height=%s", blueprint_width_m, blueprint_height_m)
return {"error": "Blueprint width and height must be numeric"}
if blueprint_width_m <= 0 or blueprint_height_m <= 0:
logger.error("Invalid blueprint dimensions: width=%s, height=%s", blueprint_width_m, blueprint_height_m)
return {"error": "Blueprint width and height must be positive"}
# Validate poppler_path
poppler_path = validate_poppler_path(poppler_path)
logger.debug("Using poppler_path: %s", poppler_path)
try:
# Convert PDF to images
logger.debug("Converting PDF to image with poppler_path=%s", poppler_path)
images = convert_from_path(
pdf_path,
first_page=1,
last_page=1,
poppler_path=poppler_path
)
if not images:
logger.error("No images extracted from PDF")
return {"error": "No images extracted from the PDF"}
logger.info("Successfully extracted %d image(s) from PDF", len(images))
# Save the first page as a temporary image
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
images[0].save(temp_file.name, 'PNG')
temp_image_path = temp_file.name
logger.debug("Saved temporary image to: %s", temp_image_path)
# Verify temporary file exists
if not os.path.exists(temp_image_path):
logger.error("Temporary image file not created: %s", temp_image_path)
return {"error": "Failed to create temporary image file"}
# Open the image with OpenCV
image = cv2.imread(temp_image_path)
if image is None:
logger.error("Failed to load image from: %s", temp_image_path)
return {"error": "Could not load the image from PDF"}
logger.info("Loaded image with dimensions: %s", image.shape)
# Clean up temporary file
os.unlink(temp_image_path)
logger.debug("Deleted temporary image file: %s", temp_image_path)
# Convert to grayscale for easier processing
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
logger.debug("Converted image to grayscale")
# Apply edge detection to find lines (walls)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
logger.debug("Applied Canny edge detection")
# Use Hough Transform to detect lines (walls)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=10)
logger.debug("Detected %s lines with Hough Transform", len(lines) if lines is not None else 0)
# Calculate total wall length (in pixels)
total_wall_length_pixels = 0
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
length = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
total_wall_length_pixels += length
logger.debug("Line from (%d,%d) to (%d,%d), length=%f pixels", x1, y1, x2, y2, length)
else:
logger.warning("No lines detected in the blueprint")
logger.info("Total wall length in pixels: %f", total_wall_length_pixels)
# Get image dimensions
image_height, image_width = image.shape[:2]
logger.debug("Image dimensions: width=%d, height=%d pixels", image_width, image_height)
# Calculate pixel-to-meter ratio
pixel_to_meter_width = blueprint_width_m / image_width
pixel_to_meter_height = blueprint_height_m / image_height
pixel_to_meter = (pixel_to_meter_width + pixel_to_meter_height) / 2
logger.debug("Pixel-to-meter ratios: width=%f, height=%f, average=%f",
pixel_to_meter_width, pixel_to_meter_height, pixel_to_meter)
# Convert wall length to meters
total_wall_length_m = total_wall_length_pixels * pixel_to_meter
logger.info("Total wall length in meters: %f", total_wall_length_m)
# Estimate wall area (assume wall height of 3 m)
wall_height_m = 3 # Hardcoded assumption; may need adjustment
wall_area = total_wall_length_m * wall_height_m
logger.info("Wall area: %f m² (wall length=%f m, height=%f m)",
wall_area, total_wall_length_m, wall_height_m)
# Estimate foundation area (10% of total blueprint area)
total_area = blueprint_width_m * blueprint_height_m
foundation_area = total_area * 0.1 # Hardcoded assumption
logger.info("Total area: %f m², Foundation area: %f m²", total_area, foundation_area)
# Calculate materials
materials = calculate_materials_from_dimensions(wall_area, foundation_area)
logger.info("Raw material estimates: %s", materials)
# Format the output
formatted_materials = {
"cement": f"{materials['cement']:.2f} kg",
"bricks": f"{materials['bricks']:.0f} units",
"steel": f"{materials['steel']:.2f} kg"
}
logger.info("Formatted material estimates: %s", formatted_materials)
return formatted_materials
except Exception as e:
# Customize error message for poppler-related issues
error_msg = str(e)
if "Unable to get page count" in error_msg:
error_msg = (
"Poppler is not installed or not in PATH. "
"Please install poppler-utils:\n"
"- Windows: Download from https://github.com/oschwartz10612/poppler-windows and add to PATH.\n"
"- Linux: Run 'sudo apt-get install poppler-utils'.\n"
"- Mac: Run 'brew install poppler'.\n"
"Alternatively, specify a valid poppler_path in the input (e.g., C:/poppler/bin)."
)
logger.error("Processing failed: %s", error_msg)
return {"error": f"Failed to process PDF: {error_msg}"}
# Set up Gradio interface
interface = gr.Interface(
fn=process_blueprint,
inputs=[
gr.File(file_types=[".pdf"], label="Upload Blueprint PDF"),
gr.Number(label="Blueprint Width (meters)", value=27),
gr.Number(label="Blueprint Height (meters)", value=9.78),
gr.Textbox(label="Poppler Path (optional)", placeholder="e.g., C:/poppler/bin")
],
outputs=gr.JSON(label="Material Estimates"),
title="Blueprint Material Estimator",
description=(
"Upload a PDF containing a blueprint to estimate construction materials. "
"Specify blueprint dimensions and provide the path to poppler binaries if not in PATH."
)
)
# Launch the interface
if __name__ == "__main__":
logger.info("Launching Gradio interface")
interface.launch(share=False)