dschandra's picture
Update app.py
65ba25a 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 for debugging without affecting output
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
poppler_path = poppler_path.replace('\\', '/') # Normalize path
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 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).
"""
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=%s, foundation_area=%s", wall_area, foundation_area)
raise ValueError("Wall and foundation areas must be numeric")
if wall_area < 0 or foundation_area < 0:
logger.error("Negative areas: 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²
materials['bricks'] += wall_area * 500 # 500 bricks per m²
materials['steel'] += wall_area * 2 # 2 kg steel per m²
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²
materials['bricks'] += foundation_area * 750 # 750 bricks per m²
materials['steel'] += foundation_area * 5 # 5 kg steel per m²
logger.debug("Foundation materials: 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 single-page PDF
def process_blueprint(pdf_path, blueprint_width_m=27, blueprint_height_m=9.78, poppler_path=None):
"""
Process a single-page PDF blueprint to estimate construction materials.
Args:
pdf_path (str): Path to the PDF file (single-page).
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 PDF file
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}"}
# Validate blueprint dimensions
if not isinstance(blueprint_width_m, (int, float)) or not isinstance(blueprint_height_m, (int, float)):
logger.error("Invalid 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 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 single-page PDF to image
logger.debug("Converting single-page PDF 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 image extracted from PDF")
return {"error": "Failed to extract image from PDF (empty or invalid PDF)"}
logger.info("Extracted 1 image from PDF")
# Save the image to a temporary file
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
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"}
# Load image with OpenCV
image = cv2.imread(temp_image_path)
if image is None:
logger.error("Failed to load image: %s", temp_image_path)
return {"error": "Could not load 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: %s", temp_image_path)
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
logger.debug("Converted image to grayscale")
# Apply edge detection
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
logger.debug("Applied Canny edge detection")
# Detect lines (walls) with Hough Transform
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=10)
logger.debug("Detected %s lines", 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 blueprint")
logger.info("Total wall length: %f pixels", 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: %f meters", total_wall_length_m)
# Estimate wall area (wall height = 3 m)
wall_height_m = 3
wall_area = total_wall_length_m * wall_height_m
logger.info("Wall area: %f m² (length=%f m, height=%f m)",
wall_area, total_wall_length_m, wall_height_m)
# Estimate foundation area (10% of total area)
total_area = blueprint_width_m * blueprint_height_m
foundation_area = total_area * 0.1
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 materials: %s", materials)
# Format output (same as image-based app)
formatted_materials = {
"cement": f"{materials['cement']:.2f} kg",
"bricks": f"{materials['bricks']:.0f} units",
"steel": f"{materials['steel']:.2f} kg"
}
logger.info("Formatted materials: %s", formatted_materials)
return formatted_materials
except Exception as e:
# Handle poppler and other errors
error_msg = str(e)
if "Unable to get page count" in error_msg or "poppler" in error_msg.lower():
error_msg = (
"Cannot convert PDF to image. Ensure poppler-utils is installed:\n"
"- Windows: Download from https://github.com/oschwartz10612/poppler-windows, extract, "
"and add the 'bin' folder (e.g., C:/poppler/bin) to PATH.\n"
"- Linux: Run 'sudo apt-get install poppler-utils'.\n"
"- Mac: Run 'brew install poppler'.\n"
"Alternatively, enter the poppler 'bin' folder path in the Poppler Path field "
"(e.g., C:/poppler/bin). Current poppler_path: %s" % poppler_path
)
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 Single-Page 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 single-page PDF blueprint to estimate construction materials. "
"Enter blueprint dimensions and, if poppler-utils is not in PATH, provide the path to poppler's 'bin' folder."
)
)
# Launch the interface
if __name__ == "__main__":
logger.info("Launching Gradio interface")
interface.launch(share=False)