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 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) |