Spaces:
Build error
Build error
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) |