File size: 11,095 Bytes
8a50c3f
 
13b9afe
de8ea7d
 
 
 
 
65ba25a
de8ea7d
 
 
 
 
 
8a50c3f
8219ccf
 
 
 
 
 
 
 
 
 
 
65ba25a
8219ccf
 
 
 
 
 
 
 
 
 
13b9afe
8a50c3f
de8ea7d
65ba25a
de8ea7d
 
 
 
 
 
 
 
 
 
65ba25a
de8ea7d
 
65ba25a
de8ea7d
 
8a50c3f
 
 
13b9afe
8a50c3f
13b9afe
 
8a50c3f
65ba25a
 
 
de8ea7d
 
13b9afe
 
8a50c3f
65ba25a
 
 
 
de8ea7d
13b9afe
de8ea7d
8a50c3f
 
65ba25a
de8ea7d
 
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
 
 
 
 
 
65ba25a
de8ea7d
 
 
65ba25a
 
de8ea7d
65ba25a
de8ea7d
 
65ba25a
de8ea7d
 
8219ccf
 
 
 
de8ea7d
65ba25a
 
de8ea7d
 
 
 
 
 
 
65ba25a
 
de8ea7d
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
 
 
65ba25a
de8ea7d
 
 
 
65ba25a
de8ea7d
 
65ba25a
 
de8ea7d
 
 
 
 
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
65ba25a
de8ea7d
 
 
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
 
 
 
 
 
 
 
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
 
 
 
 
 
 
 
 
 
 
 
65ba25a
de8ea7d
65ba25a
 
de8ea7d
65ba25a
de8ea7d
 
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
 
65ba25a
de8ea7d
65ba25a
de8ea7d
 
 
 
 
65ba25a
de8ea7d
 
 
 
65ba25a
de8ea7d
65ba25a
de8ea7d
65ba25a
 
 
de8ea7d
 
65ba25a
 
de8ea7d
 
 
13b9afe
 
8a50c3f
 
de8ea7d
65ba25a
de8ea7d
 
 
 
8a50c3f
 
de8ea7d
65ba25a
 
de8ea7d
8a50c3f
 
13b9afe
8a50c3f
de8ea7d
6a0234a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
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)