Update app.py
Browse files
app.py
CHANGED
@@ -47,7 +47,7 @@ def encode_image(image):
|
|
47 |
def resize_image(image, max_size=(800, 800)):
|
48 |
"""Resize image to avoid exceeding the API size limits."""
|
49 |
try:
|
50 |
-
image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
51 |
return image
|
52 |
except Exception as e:
|
53 |
logger.error(f"Error resizing image: {str(e)}")
|
@@ -70,29 +70,29 @@ def extract_frames_from_video(video, frame_points=[0, 0.5, 1], max_size=(800, 80
|
|
70 |
cap.release()
|
71 |
return frames
|
72 |
|
73 |
-
def
|
74 |
-
"""
|
75 |
try:
|
76 |
file_type = file.name.split('.')[-1].lower()
|
77 |
if file_type in ['jpg', 'jpeg', 'png', 'bmp']:
|
78 |
-
return
|
79 |
elif file_type in ['mp4', 'avi', 'mov', 'webm']:
|
80 |
-
return
|
81 |
else:
|
82 |
return "Unsupported file type. Please upload an image or video file."
|
83 |
except Exception as e:
|
84 |
-
logger.error(f"Error
|
85 |
-
return f"Error
|
86 |
|
87 |
-
def
|
88 |
image = Image.open(image_file.name)
|
89 |
resized_image = resize_image(image)
|
90 |
image_data_url = f"data:image/png;base64,{encode_image(resized_image)}"
|
91 |
|
92 |
-
instruction = ("You are an AI assistant specialized in
|
93 |
-
"Your task is
|
94 |
-
"
|
95 |
-
"
|
96 |
|
97 |
messages = [
|
98 |
{
|
@@ -100,7 +100,7 @@ def analyze_image(image_file):
|
|
100 |
"content": [
|
101 |
{
|
102 |
"type": "text",
|
103 |
-
"text": f"{instruction}\n\nAnalyze this image
|
104 |
},
|
105 |
{
|
106 |
"type": "image_url",
|
@@ -124,14 +124,14 @@ def analyze_image(image_file):
|
|
124 |
|
125 |
return completion.choices[0].message.content
|
126 |
|
127 |
-
def
|
128 |
frames = extract_frames_from_video(video_file.name)
|
129 |
results = []
|
130 |
|
131 |
-
instruction = ("You are an AI assistant specialized in
|
132 |
-
"Your task is
|
133 |
-
"
|
134 |
-
"
|
135 |
|
136 |
for i, frame in enumerate(frames):
|
137 |
image_data_url = f"data:image/png;base64,{encode_image(frame)}"
|
@@ -141,7 +141,7 @@ def analyze_video(video_file):
|
|
141 |
"content": [
|
142 |
{
|
143 |
"type": "text",
|
144 |
-
"text": f"{instruction}\n\nAnalyze this frame from a video (Frame {i+1}/{len(frames)})
|
145 |
},
|
146 |
{
|
147 |
"type": "image_url",
|
@@ -165,26 +165,20 @@ def analyze_video(video_file):
|
|
165 |
|
166 |
return "\n".join(results)
|
167 |
|
168 |
-
|
169 |
-
|
170 |
-
def chat_about_image(message, chat_history):
|
171 |
try:
|
172 |
-
# Prepare the conversation history for the API
|
173 |
messages = [
|
174 |
-
{"role": "system", "content": "You are an AI assistant specialized in analyzing construction site
|
175 |
]
|
176 |
|
177 |
-
# Add chat history to messages
|
178 |
for human, ai in chat_history:
|
179 |
if human:
|
180 |
messages.append({"role": "user", "content": human})
|
181 |
if ai:
|
182 |
messages.append({"role": "assistant", "content": ai})
|
183 |
|
184 |
-
# Add the new user message
|
185 |
messages.append({"role": "user", "content": message})
|
186 |
|
187 |
-
# Make API call
|
188 |
completion = client.chat.completions.create(
|
189 |
model="llama-3.2-90b-vision-preview",
|
190 |
messages=messages,
|
@@ -203,11 +197,11 @@ def chat_about_image(message, chat_history):
|
|
203 |
logger.error(f"Error during chat: {str(e)}")
|
204 |
return "", chat_history + [(message, f"Error: {str(e)}")]
|
205 |
|
206 |
-
def
|
207 |
"""
|
208 |
-
Generate a
|
209 |
"""
|
210 |
-
report = "Construction Site
|
211 |
report += "=" * 40 + "\n"
|
212 |
report += f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
213 |
|
@@ -220,22 +214,20 @@ def generate_summary_report(chat_history):
|
|
220 |
|
221 |
return report
|
222 |
|
223 |
-
def
|
224 |
"""
|
225 |
-
Generate and provide a download link for the
|
226 |
"""
|
227 |
-
report =
|
228 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
229 |
-
filename = f"
|
230 |
|
231 |
-
# Create a temporary file
|
232 |
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as temp_file:
|
233 |
temp_file.write(report)
|
234 |
temp_file_path = temp_file.name
|
235 |
|
236 |
return temp_file_path
|
237 |
|
238 |
-
|
239 |
# Custom CSS for improved styling
|
240 |
custom_css = """
|
241 |
.container { max-width: 1200px; margin: auto; padding-top: 1.5rem; }
|
@@ -269,14 +261,13 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as iface:
|
|
269 |
"""
|
270 |
<div class="container">
|
271 |
<div class="header">
|
272 |
-
<h1
|
273 |
</div>
|
274 |
-
<p class="subheader">Enhance
|
275 |
</div>
|
276 |
"""
|
277 |
)
|
278 |
|
279 |
-
# First row: Combined file upload for images and videos
|
280 |
with gr.Row():
|
281 |
file_input = gr.File(
|
282 |
label="Upload Construction Site Images or Videos",
|
@@ -285,40 +276,35 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as iface:
|
|
285 |
elem_classes="file-container"
|
286 |
)
|
287 |
|
288 |
-
# Second row: Analyze Safety Hazards Button
|
289 |
with gr.Row():
|
290 |
-
analyze_button = gr.Button("🔍
|
291 |
|
292 |
-
# Third row: Chat Interface (Safety Analysis Results)
|
293 |
with gr.Row():
|
294 |
chatbot = gr.Chatbot(
|
295 |
-
label="
|
296 |
elem_classes="chatbot",
|
297 |
-
show_share_button=False,
|
298 |
-
show_copy_button=False
|
299 |
)
|
300 |
|
301 |
-
# Fourth row: Question Bar
|
302 |
with gr.Row():
|
303 |
msg = gr.Textbox(
|
304 |
-
label="Ask about
|
305 |
-
placeholder="E.g., '
|
306 |
show_label=False,
|
307 |
elem_classes="chat-input"
|
308 |
)
|
309 |
|
310 |
-
# Fifth row: Clear Chat and Download Report Buttons
|
311 |
with gr.Row():
|
312 |
clear = gr.Button("🗑️ Clear Chat", elem_classes="clear-button")
|
313 |
download_button = gr.Button("📥 Download Report", elem_classes="download-button")
|
314 |
|
315 |
-
|
316 |
-
report_file = gr.File(label="Download Safety Analysis Report")
|
317 |
|
318 |
def process_files(files):
|
319 |
results = []
|
320 |
for file in files:
|
321 |
-
result =
|
322 |
results.append((file.name, result))
|
323 |
return results
|
324 |
|
@@ -335,11 +321,11 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as iface:
|
|
335 |
postprocess=lambda x: update_chat(chatbot.value, x)
|
336 |
)
|
337 |
|
338 |
-
msg.submit(
|
339 |
clear.click(lambda: None, None, chatbot, queue=False)
|
340 |
|
341 |
download_button.click(
|
342 |
-
|
343 |
inputs=[chatbot],
|
344 |
outputs=[report_file]
|
345 |
)
|
|
|
47 |
def resize_image(image, max_size=(800, 800)):
|
48 |
"""Resize image to avoid exceeding the API size limits."""
|
49 |
try:
|
50 |
+
image.thumbnail(max_size, Image.Resampling.LANCZOS)
|
51 |
return image
|
52 |
except Exception as e:
|
53 |
logger.error(f"Error resizing image: {str(e)}")
|
|
|
70 |
cap.release()
|
71 |
return frames
|
72 |
|
73 |
+
def detect_snags(file):
|
74 |
+
"""Detect snags in a single file (image or video)"""
|
75 |
try:
|
76 |
file_type = file.name.split('.')[-1].lower()
|
77 |
if file_type in ['jpg', 'jpeg', 'png', 'bmp']:
|
78 |
+
return detect_snags_in_image(file)
|
79 |
elif file_type in ['mp4', 'avi', 'mov', 'webm']:
|
80 |
+
return detect_snags_in_video(file)
|
81 |
else:
|
82 |
return "Unsupported file type. Please upload an image or video file."
|
83 |
except Exception as e:
|
84 |
+
logger.error(f"Error detecting snags: {str(e)}")
|
85 |
+
return f"Error detecting snags: {str(e)}"
|
86 |
|
87 |
+
def detect_snags_in_image(image_file):
|
88 |
image = Image.open(image_file.name)
|
89 |
resized_image = resize_image(image)
|
90 |
image_data_url = f"data:image/png;base64,{encode_image(resized_image)}"
|
91 |
|
92 |
+
instruction = ("You are an AI assistant specialized in detecting snags in construction sites. "
|
93 |
+
"Your task is to analyze the image and identify any construction defects, unfinished work, "
|
94 |
+
"or quality issues. List each snag, categorize it, and provide a brief description. "
|
95 |
+
"If no snags are detected, state that the area appears to be free of visible issues.")
|
96 |
|
97 |
messages = [
|
98 |
{
|
|
|
100 |
"content": [
|
101 |
{
|
102 |
"type": "text",
|
103 |
+
"text": f"{instruction}\n\nAnalyze this image for construction snags and provide a detailed report."
|
104 |
},
|
105 |
{
|
106 |
"type": "image_url",
|
|
|
124 |
|
125 |
return completion.choices[0].message.content
|
126 |
|
127 |
+
def detect_snags_in_video(video_file):
|
128 |
frames = extract_frames_from_video(video_file.name)
|
129 |
results = []
|
130 |
|
131 |
+
instruction = ("You are an AI assistant specialized in detecting snags in construction sites. "
|
132 |
+
"Your task is to analyze the video frame and identify any construction defects, unfinished work, "
|
133 |
+
"or quality issues. List each snag, categorize it, and provide a brief description. "
|
134 |
+
"If no snags are detected, state that the area appears to be free of visible issues.")
|
135 |
|
136 |
for i, frame in enumerate(frames):
|
137 |
image_data_url = f"data:image/png;base64,{encode_image(frame)}"
|
|
|
141 |
"content": [
|
142 |
{
|
143 |
"type": "text",
|
144 |
+
"text": f"{instruction}\n\nAnalyze this frame from a video (Frame {i+1}/{len(frames)}) for construction snags and provide a detailed report."
|
145 |
},
|
146 |
{
|
147 |
"type": "image_url",
|
|
|
165 |
|
166 |
return "\n".join(results)
|
167 |
|
168 |
+
def chat_about_snags(message, chat_history):
|
|
|
|
|
169 |
try:
|
|
|
170 |
messages = [
|
171 |
+
{"role": "system", "content": "You are an AI assistant specialized in analyzing construction site snags and answering questions about them. Use the information from the initial analysis to answer user queries."},
|
172 |
]
|
173 |
|
|
|
174 |
for human, ai in chat_history:
|
175 |
if human:
|
176 |
messages.append({"role": "user", "content": human})
|
177 |
if ai:
|
178 |
messages.append({"role": "assistant", "content": ai})
|
179 |
|
|
|
180 |
messages.append({"role": "user", "content": message})
|
181 |
|
|
|
182 |
completion = client.chat.completions.create(
|
183 |
model="llama-3.2-90b-vision-preview",
|
184 |
messages=messages,
|
|
|
197 |
logger.error(f"Error during chat: {str(e)}")
|
198 |
return "", chat_history + [(message, f"Error: {str(e)}")]
|
199 |
|
200 |
+
def generate_snag_report(chat_history):
|
201 |
"""
|
202 |
+
Generate a snag report from the chat history.
|
203 |
"""
|
204 |
+
report = "Construction Site Snag Detection Report\n"
|
205 |
report += "=" * 40 + "\n"
|
206 |
report += f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
207 |
|
|
|
214 |
|
215 |
return report
|
216 |
|
217 |
+
def download_snag_report(chat_history):
|
218 |
"""
|
219 |
+
Generate and provide a download link for the snag report.
|
220 |
"""
|
221 |
+
report = generate_snag_report(chat_history)
|
222 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
223 |
+
filename = f"snag_detection_report_{timestamp}.txt"
|
224 |
|
|
|
225 |
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as temp_file:
|
226 |
temp_file.write(report)
|
227 |
temp_file_path = temp_file.name
|
228 |
|
229 |
return temp_file_path
|
230 |
|
|
|
231 |
# Custom CSS for improved styling
|
232 |
custom_css = """
|
233 |
.container { max-width: 1200px; margin: auto; padding-top: 1.5rem; }
|
|
|
261 |
"""
|
262 |
<div class="container">
|
263 |
<div class="header">
|
264 |
+
<h1>🔍 Construction Site Snag Detector</h1>
|
265 |
</div>
|
266 |
+
<p class="subheader">Enhance quality control and project management with AI-powered snag detection using Llama 3.2 90B Vision and expert chat assistance.</p>
|
267 |
</div>
|
268 |
"""
|
269 |
)
|
270 |
|
|
|
271 |
with gr.Row():
|
272 |
file_input = gr.File(
|
273 |
label="Upload Construction Site Images or Videos",
|
|
|
276 |
elem_classes="file-container"
|
277 |
)
|
278 |
|
|
|
279 |
with gr.Row():
|
280 |
+
analyze_button = gr.Button("🔍 Detect Snags", elem_classes="analyze-button")
|
281 |
|
|
|
282 |
with gr.Row():
|
283 |
chatbot = gr.Chatbot(
|
284 |
+
label="Snag Detection Results and Expert Chat",
|
285 |
elem_classes="chatbot",
|
286 |
+
show_share_button=False,
|
287 |
+
show_copy_button=False
|
288 |
)
|
289 |
|
|
|
290 |
with gr.Row():
|
291 |
msg = gr.Textbox(
|
292 |
+
label="Ask about detected snags or quality issues",
|
293 |
+
placeholder="E.g., 'What are the most critical snags detected?'",
|
294 |
show_label=False,
|
295 |
elem_classes="chat-input"
|
296 |
)
|
297 |
|
|
|
298 |
with gr.Row():
|
299 |
clear = gr.Button("🗑️ Clear Chat", elem_classes="clear-button")
|
300 |
download_button = gr.Button("📥 Download Report", elem_classes="download-button")
|
301 |
|
302 |
+
report_file = gr.File(label="Download Snag Detection Report")
|
|
|
303 |
|
304 |
def process_files(files):
|
305 |
results = []
|
306 |
for file in files:
|
307 |
+
result = detect_snags(file)
|
308 |
results.append((file.name, result))
|
309 |
return results
|
310 |
|
|
|
321 |
postprocess=lambda x: update_chat(chatbot.value, x)
|
322 |
)
|
323 |
|
324 |
+
msg.submit(chat_about_snags, [msg, chatbot], [msg, chatbot])
|
325 |
clear.click(lambda: None, None, chatbot, queue=False)
|
326 |
|
327 |
download_button.click(
|
328 |
+
download_snag_report,
|
329 |
inputs=[chatbot],
|
330 |
outputs=[report_file]
|
331 |
)
|