iplotnor's picture
Update app.py
587fbb7 verified
import gradio as gr
import google.generativeai as genai
import os
import json
import fitz # PyMuPDF
from PIL import Image
import io
import tempfile
import time
import re
import math
from typing import List, Dict, Any, Union, Tuple
# Configure the Gemini API (will use environment variable in production)
def configure_genai_api():
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
# For local testing, you can set a default key
api_key = os.environ.get("GOOGLE_API_KEY")
if not api_key:
return False
genai.configure(api_key=api_key)
return True
# Function to extract images from PDF
def extract_images_from_pdf(pdf_file):
pdf_document = fitz.open(stream=pdf_file, filetype="pdf")
images = []
page_images = []
for page_num in range(len(pdf_document)):
page = pdf_document[page_num]
# First try to get embedded images
image_list = page.get_images(full=True)
# If no embedded images, render the page as an image
if not image_list:
pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
img_bytes = pix.tobytes("png")
img = Image.open(io.BytesIO(img_bytes))
images.append(img)
page_images.append((page_num + 1, img))
else:
# Extract embedded images
for img_index, img_info in enumerate(image_list):
xref = img_info[0]
base_image = pdf_document.extract_image(xref)
image_bytes = base_image["image"]
img = Image.open(io.BytesIO(image_bytes))
# Filter out very small images (likely icons or decorations)
if img.width > 100 and img.height > 100:
images.append(img)
page_images.append((page_num + 1, img))
# If no images were found, render all pages as images
if not images:
for page_num in range(len(pdf_document)):
page = pdf_document[page_num]
pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
img_bytes = pix.tobytes("png")
img = Image.open(io.BytesIO(img_bytes))
images.append(img)
page_images.append((page_num + 1, img))
return images, page_images
# Extract text from PDF to find measurement scales and dimensions
def extract_measurement_info(pdf_file):
try:
pdf_document = fitz.open(stream=pdf_file, filetype="pdf")
measurement_info = {
"scale": None,
"ceiling_height": None,
"room_dimensions": {}
}
scale_patterns = [
r'(?i)scale\s*1\s*:\s*(\d+)',
r'(?i)målestokk\s*1\s*:\s*(\d+)',
r'(?i)skala\s*1\s*:\s*(\d+)',
r'1\s*:\s*(\d+)'
]
height_patterns = [
r'(?i)ceiling\s*height\s*[=:]?\s*(\d+[\.,]?\d*)\s*m',
r'(?i)takhøyde\s*[=:]?\s*(\d+[\.,]?\d*)\s*m',
r'(?i)høyde\s*[=:]?\s*(\d+[\.,]?\d*)\s*m',
r'(?i)romhøyde\s*[=:]?\s*(\d+[\.,]?\d*)\s*m',
r'(?i)h\s*[=:]?\s*(\d+[\.,]?\d*)\s*m'
]
room_dim_patterns = [
r'(?i)(stue|kjøkken|soverom|bad|gang|entré|kontor|bod).*?(\d+[\.,]?\d*)\s*[xX×]\s*(\d+[\.,]?\d*)',
r'(?i)(living|kitchen|bedroom|bathroom|hallway|entrance|office|storage).*?(\d+[\.,]?\d*)\s*[xX×]\s*(\d+[\.,]?\d*)'
]
for page_num in range(len(pdf_document)):
page = pdf_document[page_num]
text = page.get_text()
# Look for scale information
if not measurement_info["scale"]:
for pattern in scale_patterns:
matches = re.findall(pattern, text)
if matches:
measurement_info["scale"] = int(matches[0])
break
# Look for ceiling height
if not measurement_info["ceiling_height"]:
for pattern in height_patterns:
matches = re.findall(pattern, text)
if matches:
height = matches[0].replace(',', '.')
measurement_info["ceiling_height"] = float(height)
break
# Look for room dimensions
for pattern in room_dim_patterns:
matches = re.findall(pattern, text)
for match in matches:
room_name = match[0].lower().strip()
width = float(match[1].replace(',', '.'))
length = float(match[2].replace(',', '.'))
measurement_info["room_dimensions"][room_name] = {
"width": width,
"length": length
}
# Default values if not found
if not measurement_info["scale"]:
measurement_info["scale"] = 100 # Common scale for residential floor plans
if not measurement_info["ceiling_height"]:
measurement_info["ceiling_height"] = 2.4 # Standard ceiling height in Norway
return measurement_info
except Exception as e:
print(f"Error extracting measurement info: {e}")
return {
"scale": 100,
"ceiling_height": 2.4,
"room_dimensions": {}
}
# Norwegian room translations
NORWEGIAN_ROOM_NAMES = {
"stue": "living room",
"kjøkken": "kitchen",
"soverom": "bedroom",
"bad": "bathroom",
"toalett": "toilet",
"gang": "hallway",
"entré": "entrance",
"garderobe": "wardrobe",
"balkong": "balcony",
"terrasse": "terrace",
"kontor": "office",
"vaskerom": "laundry room",
"bod": "storage room",
"garasje": "garage",
"trapp": "stairs",
"spisestue": "dining room",
"arbeidsrom": "study",
"loftstue": "loft",
"kjeller": "basement",
"gjesteværelse": "guest room"
}
# The prompt template for Gemini - with Norwegian context and measurement instructions
def create_prompt(floor_plan_description=None, measurement_info=None):
# Add measurement information to the prompt if available
measurement_context = ""
if measurement_info:
measurement_context = f"""
**Important Measurement Information:**
* The floor plan uses a scale of 1:{measurement_info['scale']}
* The standard ceiling height is {measurement_info['ceiling_height']} meters
"""
if measurement_info["room_dimensions"]:
measurement_context += "* Known room dimensions (width × length in meters):\n"
for room, dims in measurement_info["room_dimensions"].items():
measurement_context += f" - {room}: {dims['width']} × {dims['length']} meters\n"
prompt = f"""You are an expert architectural assistant. I am providing a PDF floor plan of a house from Norway. The floor plan is likely to contain Norwegian text and terminology. Your job is to analyze the layout and extract **complete room-level information** in **structured JSON format** that I can directly use to build a 3D model.
{measurement_context}
Please return the following **JSON structure**, including estimates and spatial relationships:
```
[
{{
"name": "Room name",
"name_no": "Norwegian room name (if present)",
"area_m2": 0.0,
"position": "approximate location (e.g., north, south-east corner)",
"dimensions_m": {{
"width": 0.0,
"length": 0.0
}},
"windows": 0,
"window_positions": ["north wall", "east wall"],
"doors": 0,
"door_positions": ["interior", "to terrace"],
"connected_rooms": ["Room A", "Room B"],
"has_external_access": true,
"ceiling_height_m": 2.4,
"furniture": ["sofa", "kitchen island"],
"estimated": false
}}
]
```
**Instructions:**
* Include **all rooms**, including utility spaces (garage, hallway, terrace, storage, laundry, etc.).
* Recognize Norwegian room names and provide both Norwegian (name_no) and English (name) versions.
* Calculate area_m2 accurately as width × length. Double-check this calculation for all rooms.
* If any values are not labeled, **estimate them** based on layout scale and set `"estimated": true`.
* Try to determine the **direction/position** of each room (e.g., "northwest corner", "center").
* Count and identify **doors and windows**, and specify on which **wall** they are located.
* Include `"has_external_access": true` if a room connects directly outside (e.g., terrace, garage, entrance).
* Optionally include `"furniture"` if visible or labeled in the plan.
* Use the ceiling height of {measurement_info["ceiling_height"] if measurement_info else 2.4}m if not otherwise specified.
* **Only return the JSON array. Do not add explanations or extra text.**
**Common Norwegian architectural terms:**
* Stue = Living room
* Kjøkken = Kitchen
* Soverom = Bedroom
* Bad = Bathroom
* Toalett = Toilet
* Gang = Hallway
* Entré = Entrance
* Garderobe = Wardrobe
* Balkong = Balcony
* Terrasse = Terrace
* Kontor = Office
* Vaskerom = Laundry room
* Bod = Storage room
* Garasje = Garage
* Trapp = Stairs
* Spisestue = Dining room
"""
if floor_plan_description:
prompt += f"\n\nAdditional information about the floor plan: {floor_plan_description}"
return prompt
# Function to post-process and validate the JSON results
def validate_and_fix_measurements(json_data, measurement_info=None):
try:
if isinstance(json_data, str):
data = json.loads(json_data)
else:
data = json_data
if not isinstance(data, list):
return json_data
default_ceiling_height = measurement_info.get("ceiling_height", 2.4) if measurement_info else 2.4
for room in data:
# Fix ceiling height
if room.get("ceiling_height_m") is None or room.get("ceiling_height_m") <= 0:
room["ceiling_height_m"] = default_ceiling_height
room["estimated"] = True
# Check dimensions and area
if "dimensions_m" in room:
width = room["dimensions_m"].get("width", 0)
length = room["dimensions_m"].get("length", 0)
# If we have dimensions but they're invalid, try to fix them
if width <= 0 or length <= 0:
# Check if we have area to calculate dimensions
if room.get("area_m2", 0) > 0:
# Approximate dimensions using a square root (assuming square-ish room)
side = math.sqrt(room["area_m2"])
room["dimensions_m"]["width"] = round(side, 1)
room["dimensions_m"]["length"] = round(side, 1)
room["estimated"] = True
else:
# Set reasonable defaults
room["dimensions_m"]["width"] = 3.0
room["dimensions_m"]["length"] = 3.0
room["area_m2"] = 9.0
room["estimated"] = True
else:
# Recalculate area based on dimensions
calculated_area = width * length
current_area = room.get("area_m2", 0)
# If area is missing or significantly different, update it
if current_area <= 0 or abs(current_area - calculated_area) > 0.5:
room["area_m2"] = round(calculated_area, 1)
# If we have area but no dimensions, calculate them
elif "area_m2" in room and room["area_m2"] > 0:
# Approximate dimensions using a square root (assuming square-ish room)
side = math.sqrt(room["area_m2"])
room["dimensions_m"] = {
"width": round(side, 1),
"length": round(side, 1)
}
room["estimated"] = True
# Check if we have room dimensions in our measurement info
if measurement_info and "room_dimensions" in measurement_info:
room_name_lower = room.get("name", "").lower()
room_name_no_lower = room.get("name_no", "").lower()
# Check both English and Norwegian names
for name in [room_name_lower, room_name_no_lower]:
if name in measurement_info["room_dimensions"]:
known_dims = measurement_info["room_dimensions"][name]
room["dimensions_m"] = {
"width": known_dims["width"],
"length": known_dims["length"]
}
room["area_m2"] = round(known_dims["width"] * known_dims["length"], 1)
room["estimated"] = False
break
return json.dumps(data, indent=2)
except Exception as e:
print(f"Error validating measurements: {e}")
return json_data
# Function to call Gemini model
def analyze_floor_plan(images, description=None, model_name='gemini-1.5-flash', measurement_info=None):
try:
api_configured = configure_genai_api()
if not api_configured:
return json.dumps({
"error": "Gemini API key not configured. Please set the GEMINI_API_KEY environment variable."
})
# Use specified Gemini model
model = genai.GenerativeModel(model_name)
# Create prompt with optional description and measurement info
prompt = create_prompt(description, measurement_info)
# Call Gemini with the images and prompt
response = model.generate_content([prompt, *images])
# Extract JSON from the response
response_text = response.text
# Find JSON content between triple backticks if present
json_text = None
if "```" in response_text:
parts = response_text.split("```")
for i, part in enumerate(parts):
part = part.strip()
# Skip empty parts and parts that are just language identifiers
if not part or part.lower() in ["json", "javascript"]:
continue
# Try to parse this part as JSON
try:
json.loads(part)
json_text = part
break
except:
# If this part is followed by another part and it might be a code block
if i + 1 < len(parts):
try:
# Sometimes the JSON content is split across parts strangely
combined = part + parts[i+1].strip()
json.loads(combined)
json_text = combined
break
except:
pass
# If we still didn't find valid JSON, try the second part with common fixes
if not json_text and len(parts) > 1:
potential_json = parts[1].strip()
# Remove "json" language identifier if present
if potential_json.lower().startswith("json"):
potential_json = potential_json[4:].strip()
# Try to fix common JSON issues
potential_json = potential_json.replace("'", '"') # Replace single quotes with double quotes
try:
json.loads(potential_json)
json_text = potential_json
except:
pass
else:
# If no backticks, try the entire response
try:
json.loads(response_text.strip())
json_text = response_text.strip()
except:
pass
# If we found and validated JSON, return it
if json_text:
try:
parsed_json = json.loads(json_text)
# Validate and fix measurements
validated_json = validate_and_fix_measurements(parsed_json, measurement_info)
return validated_json
except:
# Final fallback - return the response as an error
return json.dumps({
"error": "Could not parse JSON from model response",
"raw_response": response_text
})
else:
# No valid JSON found
return json.dumps({
"error": "Could not extract valid JSON from model response",
"raw_response": response_text
})
except Exception as e:
# Return a valid JSON with error message
return json.dumps({
"error": f"Error analyzing floor plan: {str(e)}"
})
# Function to display the parsed JSON in a more user-friendly view
def create_json_visualization(json_data):
try:
# Try to parse the JSON
data = json.loads(json_data)
# Check if it's an error message
if isinstance(data, dict) and "error" in data:
return f"""
<div style="border: 1px solid #f44336; border-radius: 8px; padding: 16px; background-color: #ffebee;">
<h3 style="color: #d32f2f; margin-top: 0;">Error</h3>
<p>{data["error"]}</p>
{f'<pre style="background-color: #f5f5f5; padding: 8px; border-radius: 4px; overflow: auto;">{data.get("raw_response", "")}</pre>' if "raw_response" in data else ""}
</div>
"""
# If it's an empty list, show message
if isinstance(data, list) and len(data) == 0:
return """
<div style="border: 1px solid #ff9800; border-radius: 8px; padding: 16px; background-color: #fff8e1;">
<h3 style="color: #ef6c00; margin-top: 0;">Ingen rom oppdaget / No Rooms Detected</h3>
<p>Analysen oppdaget ingen rom i plantegningen. Dette kan skyldes: / The analysis did not detect any rooms in the provided floor plan. This might be due to:</p>
<ul>
<li>Dårlig bildekvalitet / Low image quality in the floor plan</li>
<li>Uvanlig plantegningsformat / Non-standard floor plan format</li>
<li>Manglende romnavn eller grenser / Missing room labels or boundaries</li>
</ul>
<p>Prøv å laste opp en tydeligere plantegning eller gi mer kontekst i beskrivelsen. / Try uploading a clearer floor plan or providing more context in the description.</p>
</div>
"""
# If we have valid room data, create the visualization
html = """
<style>
.room-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
background-color: #f9f9f9;
}
.room-name {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.norwegian-name {
font-style: italic;
color: #555;
font-size: 14px;
margin-left: 8px;
}
.room-details {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
.detail-group {
margin-bottom: 8px;
}
.detail-label {
font-weight: 600;
color: #555;
}
.detail-value {
color: #333;
}
.estimated-badge {
display: inline-block;
background-color: #ffcc00;
color: #333;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
margin-left: 8px;
}
</style>
"""
# Add summary information at the top
total_area = sum(room.get("area_m2", 0) for room in data)
html += f"""
<div style="margin-bottom: 20px; padding: 12px; background-color: #e3f2fd; border-radius: 8px;">
<h3 style="margin-top: 0; color: #0d47a1;">Romoppsummering / Room Summary</h3>
<p>Totalt antall rom / Total rooms: <strong>{len(data)}</strong></p>
<p>Total areal / Total area: <strong>{total_area:.1f} m²</strong></p>
</div>
"""
for room in data:
estimated = room.get("estimated", False)
estimated_badge = '<span class="estimated-badge">Estimated</span>' if estimated else ''
norwegian_name = f'<span class="norwegian-name">({room.get("name_no", "")})</span>' if "name_no" in room and room["name_no"] else ''
html += f"""
<div class="room-card">
<div class="room-name">{room.get("name", "Unnamed Room")} {norwegian_name} {estimated_badge}</div>
<div class="room-details">
<div class="detail-group">
<div class="detail-label">Area:</div>
<div class="detail-value">{room.get("area_m2", "N/A")} m²</div>
</div>
<div class="detail-group">
<div class="detail-label">Position:</div>
<div class="detail-value">{room.get("position", "N/A")}</div>
</div>
<div class="detail-group">
<div class="detail-label">Dimensions:</div>
<div class="detail-value">
{room.get("dimensions_m", {}).get("width", "N/A")} × {room.get("dimensions_m", {}).get("length", "N/A")} m
</div>
</div>
<div class="detail-group">
<div class="detail-label">Windows:</div>
<div class="detail-value">{room.get("windows", "N/A")}</div>
</div>
<div class="detail-group">
<div class="detail-label">Doors:</div>
<div class="detail-value">{room.get("doors", "N/A")}</div>
</div>
<div class="detail-group">
<div class="detail-label">Ceiling Height:</div>
<div class="detail-value">{room.get("ceiling_height_m", "N/A")} m</div>
</div>
</div>
<div style="margin-top: 10px;">
<div class="detail-label">Window Positions:</div>
<div class="detail-value">{", ".join(room.get("window_positions", ["None"]))}</div>
</div>
<div style="margin-top: 10px;">
<div class="detail-label">Door Positions:</div>
<div class="detail-value">{", ".join(room.get("door_positions", ["None"]))}</div>
</div>
<div style="margin-top: 10px;">
<div class="detail-label">Connected Rooms:</div>
<div class="detail-value">{", ".join(room.get("connected_rooms", ["None"]))}</div>
</div>
<div style="margin-top: 10px;">
<div class="detail-label">External Access:</div>
<div class="detail-value">{"Yes" if room.get("has_external_access", False) else "No"}</div>
</div>
<div style="margin-top: 10px;">
<div class="detail-label">Furniture:</div>
<div class="detail-value">{", ".join(room.get("furniture", ["None"]))}</div>
</div>
</div>
"""
return html
except Exception as e:
return f"""
<div style="border: 1px solid #f44336; border-radius: 8px; padding: 16px; background-color: #ffebee;">
<h3 style="color: #d32f2f; margin-top: 0;">Error Visualizing Data</h3>
<p>{str(e)}</p>
<pre style="background-color: #f5f5f5; padding: 8px; border-radius: 4px; overflow: auto;">{json_data}</pre>
</div>
"""
# Function to save JSON to file
def save_json_to_file(json_data, prefix="floor_plan_analysis"):
if not json_data:
return None
try:
# Create temporary file
timestamp = time.strftime("%Y%m%d-%H%M%S")
filename = f"{prefix}_{timestamp}.json"
with tempfile.NamedTemporaryFile(delete=False, suffix=".json", mode="w") as f:
f.write(json_data)
return f.name
except Exception as e:
print(f"Error saving JSON to file: {str(e)}")
return None
# Gradio interface
def create_interface():
with gr.Blocks(title="Floor Plan Analyzer", theme=gr.themes.Soft()) as app:
# Header
gr.Markdown("""
# 🏠 Arkitektonisk Plantegningsanalysator / Architectural Floor Plan Analyzer
Last opp en PDF av en arkitektonisk plantegning for å få detaljert rominformasjon i JSON-format.
Upload a PDF of an architectural floor plan to get detailed room-level information in JSON format.
Dette verktøyet bruker Google's Gemini Pro Vision-modell for å analysere plantegninger og ekstrahere strukturerte data.
This tool uses Google's Gemini Pro Vision model to analyze floor plans and extract structured data.
""")
# Main content area
with gr.Tabs():
# Upload and analyze tab
with gr.TabItem("Analyser Plantegning / Analyze Floor Plan"):
with gr.Row():
# Left column - Inputs
with gr.Column(scale=2):
# Model selection
model_dropdown = gr.Dropdown(
choices=["gemini-1.5-flash", "gemini-1.5-pro-latest"],
label="Velg Gemini-modell / Select Gemini Model",
value="gemini-1.5-flash",
interactive=True
)
# File upload
pdf_input = gr.File(
label="Last opp plantegning PDF / Upload Floor Plan PDF",
file_types=[".pdf"],
type="binary"
)
# Description field
description_input = gr.Textbox(
label="Valgfri beskrivelse / Optional Description",
placeholder="F.eks. 'Dette er et hus med 3 soverom' / E.g., 'This is a house with 3 bedrooms'",
lines=3
)
# Analysis button
with gr.Row():
analyze_button = gr.Button("Analyser Plantegning / Analyze Floor Plan", variant="primary", scale=2)
clear_button = gr.Button("Tøm / Clear", variant="secondary", scale=1)
# Right column - Outputs
with gr.Column(scale=3):
# Status message
status_message = gr.Markdown("Last opp en plantegning PDF og klikk 'Analyser' for å starte / Upload a PDF floor plan and click 'Analyze' to start")
# Progress indicator
with gr.Row(visible=False) as progress_row:
progress = gr.Textbox(value="Analyserer plantegning... / Analyzing floor plan...", label="Status")
# Image gallery
image_output = gr.Gallery(
label="Ekstraherte plantegningsbilder / Extracted Floor Plan Images",
columns=2,
rows=2,
height=400,
object_fit="contain"
)
# JSON results area (tabbed output)
with gr.Tabs():
with gr.TabItem("JSON Output"):
output_json = gr.JSON(label="Romanalyseresultat / Room Analysis Result")
download_button = gr.Button("Last ned JSON / Download JSON", variant="secondary")
json_file_output = gr.File(label="Last ned JSON-fil / Download JSON File", visible=False)
with gr.TabItem("Visuell oppsummering / Visual Summary"):
html_output = gr.HTML(label="Romanalysevisualisering / Room Analysis Visualization")
# About tab
with gr.TabItem("Om verktøyet / About & Help"):
gr.Markdown("""
## Om dette verktøyet / About This Tool
Denne Arkitektoniske Plantegningsanalysatoren bruker Google's Gemini AI for å ekstrahere strukturert informasjon fra plantegninger i PDF-format.
This Architectural Floor Plan Analyzer uses Google's Gemini AI to extract structured information from floor plan PDFs.
### Hvordan det fungerer / How it Works
1. **Last opp en PDF / Upload a PDF**: Verktøyet ekstraherer bilder fra din plantegnings-PDF
2. **Analyse / Analysis**: Gemini AI-modellen undersøker plantegningen og identifiserer rom, dimensjoner og funksjoner
3. **Resultater / Results**: Du mottar en strukturert JSON-utgang med detaljert informasjon om hvert rom
### Utgangsformat / Output Format
JSON-utdataene inkluderer / The JSON output includes:
- Romnavn og plasseringer / Room names and locations
- Areal og dimensjoner / Area and dimensions
- Vinduer og dører (antall og posisjoner) / Windows and doors (count and positions)
- Tilkoblede rom / Connected rooms
- Informasjon om ekstern tilgang / External access information
- Møbler (hvis synlig i planen) / Furniture (if visible in the plan)
- Takhøyde (standard: 2,4 m) / Ceiling height (default: 2.4m)
### Tips for beste resultater / Tips for Best Results
- Bruk klare plantegninger av høy kvalitet / Use clear, high-quality floor plans
- Sørg for at romnavnene er synlige / Make sure room labels are visible
- Gi ytterligere kontekst i beskrivelsefeltet om nødvendig / Provide additional context in the description field if needed
- Prøv forskjellige modeller hvis resultatene ikke er tilfredsstillende / Try different models if results are not satisfactory
### Krav / Requirements
Dette verktøyet krever en gyldig Gemini API-nøkkel som er satt som en miljøvariabel.
This tool requires a valid Gemini API key to be set as an environment variable.
For spørsmål eller problemer, vennligst kontakt utvikleren.
For questions or issues, please contact the developer.
""")
# Event handlers
def process_pdf(pdf_file, description, model_name):
if pdf_file is None:
return (
gr.update(visible=False),
"Vennligst last opp en PDF-fil. / Please upload a PDF file.",
None,
json.dumps({"error": "No PDF file uploaded"}),
"<div>Error: No PDF file uploaded</div>",
gr.update(value=None)
)
# Show progress
yield (
gr.update(visible=True),
"Ekstraherer bilder fra PDF... / Extracting images from PDF...",
None,
None,
None,
gr.update(value=None)
)
try:
# Extract images from PDF
images, page_images = extract_images_from_pdf(pdf_file)
if not images:
error_json = json.dumps({"error": "Could not extract any images from the PDF"})
yield (
gr.update(visible=False),
"Feil: Kunne ikke ekstrahere bilder fra PDF. / Error: Could not extract any images from the PDF.",
None,
error_json,
create_json_visualization(error_json),
gr.update(value=None)
)
return
# Update progress
yield (
gr.update(visible=True),
"Analyserer plantegning med Gemini AI... / Analyzing floor plan with Gemini AI...",
[img[1] for img in page_images],
None,
None,
gr.update(value=None)
)
# Call Gemini for analysis
json_result = analyze_floor_plan(images, description, model_name)
# Create visualization
html_viz = create_json_visualization(json_result)
# Save JSON to file
json_file_path = save_json_to_file(json_result)
# Hide progress
yield (
gr.update(visible=False),
"Analyse fullført! / Analysis complete!",
[img[1] for img in page_images],
json_result,
html_viz,
gr.update(value=json_file_path)
)
except Exception as e:
error_msg = str(e)
error_json = json.dumps({"error": f"Error processing PDF: {error_msg}"})
error_html = create_json_visualization(error_json)
yield (
gr.update(visible=False),
f"Feil: {error_msg} / Error: {error_msg}",
None,
error_json,
error_html,
gr.update(value=None)
)
def clear_outputs():
return (
gr.update(visible=False),
"Last opp en plantegning PDF og klikk 'Analyser' for å starte / Upload a PDF floor plan and click 'Analyze' to start",
None,
None,
None,
gr.update(value=None)
)
analyze_button.click(
fn=process_pdf,
inputs=[pdf_input, description_input, model_dropdown],
outputs=[progress_row, status_message, image_output, output_json, html_output, json_file_output]
)
clear_button.click(
fn=clear_outputs,
inputs=[],
outputs=[progress_row, status_message, image_output, output_json, html_output, json_file_output]
)
download_button.click(
fn=lambda x: x,
inputs=[json_file_output],
outputs=[json_file_output]
)
return app
# Create and launch the interface
demo = create_interface()
# For local testing
if __name__ == "__main__":
demo.launch()
else:
# For Hugging Face Spaces
demo.launch(share=False)