File size: 11,110 Bytes
8a50c3f
 
13b9afe
de8ea7d
 
 
 
 
 
 
 
 
 
 
 
8a50c3f
8219ccf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13b9afe
8a50c3f
de8ea7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a50c3f
 
 
13b9afe
8a50c3f
13b9afe
 
8a50c3f
13b9afe
 
de8ea7d
 
 
13b9afe
 
8a50c3f
13b9afe
 
de8ea7d
 
 
13b9afe
de8ea7d
8a50c3f
 
de8ea7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8219ccf
 
 
 
de8ea7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8219ccf
de8ea7d
 
 
13b9afe
 
8a50c3f
 
de8ea7d
 
 
 
 
 
8a50c3f
 
de8ea7d
 
8219ccf
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
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)