Update app.py
Browse files
app.py
CHANGED
@@ -4,9 +4,13 @@ from PIL import Image
|
|
4 |
import torch
|
5 |
import time
|
6 |
import gc
|
|
|
7 |
from knowledge_base import KNOWLEDGE_BASE, DAMAGE_TYPES
|
8 |
from rag_utils import RAGSystem
|
9 |
|
|
|
|
|
|
|
10 |
# Constants
|
11 |
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
|
12 |
MAX_IMAGE_SIZE = 1024 # Maximum dimension for images
|
@@ -16,317 +20,146 @@ MODEL = None
|
|
16 |
PROCESSOR = None
|
17 |
RAG_SYSTEM = None
|
18 |
|
|
|
19 |
def cleanup_memory():
|
20 |
"""Clean up memory and GPU cache"""
|
21 |
gc.collect()
|
22 |
if torch.cuda.is_available():
|
23 |
torch.cuda.empty_cache()
|
24 |
|
25 |
-
|
26 |
-
"""Initialize session state variables"""
|
27 |
-
if 'history' not in st.session_state:
|
28 |
-
st.session_state.history = []
|
29 |
-
if 'dark_mode' not in st.session_state:
|
30 |
-
st.session_state.dark_mode = False
|
31 |
-
|
32 |
@st.cache_resource(show_spinner="Loading AI model...")
|
33 |
def load_model():
|
34 |
"""Load and cache the model and processor"""
|
35 |
try:
|
36 |
model_name = "google/vit-base-patch16-224"
|
37 |
-
# Initialize the processor first
|
38 |
processor = ViTImageProcessor.from_pretrained(model_name)
|
39 |
-
|
40 |
-
# Load model with specific device configuration
|
41 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
|
42 |
model = ViTForImageClassification.from_pretrained(
|
43 |
model_name,
|
44 |
num_labels=len(DAMAGE_TYPES),
|
45 |
ignore_mismatched_sizes=True,
|
46 |
).to(device)
|
47 |
-
|
48 |
-
# Ensure model is in evaluation mode
|
49 |
model.eval()
|
50 |
-
|
51 |
return model, processor
|
52 |
except Exception as e:
|
53 |
-
|
|
|
54 |
return None, None
|
55 |
|
|
|
|
|
56 |
def init_rag_system():
|
57 |
-
"""Initialize the RAG system with knowledge base"""
|
58 |
global RAG_SYSTEM
|
59 |
-
|
60 |
RAG_SYSTEM = RAGSystem()
|
61 |
RAG_SYSTEM.initialize_knowledge_base(KNOWLEDGE_BASE)
|
|
|
|
|
|
|
|
|
62 |
|
|
|
63 |
def validate_image(image):
|
64 |
-
"""Validate image size and format"""
|
65 |
if image.size[0] * image.size[1] > 1024 * 1024:
|
66 |
-
st.warning("Large image detected.
|
67 |
if image.format not in ['JPEG', 'PNG']:
|
68 |
-
st.warning("
|
69 |
|
|
|
70 |
def preprocess_image(uploaded_file):
|
71 |
-
"""Preprocess and validate uploaded image"""
|
72 |
try:
|
73 |
image = Image.open(uploaded_file)
|
74 |
-
# Resize if image is too large
|
75 |
if max(image.size) > MAX_IMAGE_SIZE:
|
76 |
ratio = MAX_IMAGE_SIZE / max(image.size)
|
77 |
new_size = tuple([int(dim * ratio) for dim in image.size])
|
78 |
image = image.resize(new_size, Image.Resampling.LANCZOS)
|
79 |
return image
|
80 |
except Exception as e:
|
81 |
-
|
|
|
82 |
return None
|
83 |
|
|
|
84 |
def analyze_damage(image, model, processor):
|
85 |
-
"""Analyze structural damage in the image"""
|
86 |
try:
|
87 |
device = next(model.parameters()).device
|
88 |
with torch.no_grad():
|
89 |
image = image.convert('RGB')
|
90 |
inputs = processor(images=image, return_tensors="pt")
|
91 |
-
# Move inputs to the same device as model
|
92 |
inputs = {k: v.to(device) for k, v in inputs.items()}
|
93 |
outputs = model(**inputs)
|
94 |
probs = torch.nn.functional.softmax(outputs.logits, dim=1)[0]
|
95 |
cleanup_memory()
|
96 |
-
return probs.cpu()
|
97 |
-
except
|
98 |
-
|
99 |
-
|
100 |
-
st.error("Out of memory. Please try with a smaller image.")
|
101 |
-
else:
|
102 |
-
st.error(f"Error analyzing image: {str(e)}")
|
103 |
return None
|
104 |
|
105 |
-
|
106 |
-
"""Return custom CSS styles"""
|
107 |
-
return """
|
108 |
-
<style>
|
109 |
-
.main {
|
110 |
-
padding: 2rem;
|
111 |
-
}
|
112 |
-
.stProgress > div > div > div > div {
|
113 |
-
background-image: linear-gradient(to right, var(--progress-color, #ff6b6b), var(--progress-color-end, #f06595));
|
114 |
-
}
|
115 |
-
.damage-card {
|
116 |
-
padding: 1.5rem;
|
117 |
-
border-radius: 0.5rem;
|
118 |
-
background: var(--card-bg, #f8f9fa);
|
119 |
-
margin-bottom: 1rem;
|
120 |
-
border: 1px solid var(--border-color, #dee2e6);
|
121 |
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
122 |
-
}
|
123 |
-
.damage-header {
|
124 |
-
font-size: 1.25rem;
|
125 |
-
font-weight: bold;
|
126 |
-
margin-bottom: 1rem;
|
127 |
-
color: var(--text-color, #212529);
|
128 |
-
}
|
129 |
-
.dark-mode {
|
130 |
-
background-color: #1a1a1a;
|
131 |
-
color: #ffffff;
|
132 |
-
}
|
133 |
-
.dark-mode .damage-card {
|
134 |
-
background: #2d2d2d;
|
135 |
-
border-color: #404040;
|
136 |
-
}
|
137 |
-
</style>
|
138 |
-
"""
|
139 |
-
|
140 |
-
def display_header():
|
141 |
-
"""Display application header"""
|
142 |
-
st.markdown(
|
143 |
-
"""
|
144 |
-
<div style='text-align: center; padding: 1rem;'>
|
145 |
-
<h1>🏗️ Structural Damage Analyzer Pro</h1>
|
146 |
-
<p style='font-size: 1.2rem;'>Advanced AI-powered structural damage assessment tool</p>
|
147 |
-
</div>
|
148 |
-
""",
|
149 |
-
unsafe_allow_html=True
|
150 |
-
)
|
151 |
-
|
152 |
def display_enhanced_analysis(damage_type, confidence):
|
153 |
-
"""Display enhanced analysis from RAG system"""
|
154 |
try:
|
155 |
enhanced_info = RAG_SYSTEM.get_enhanced_analysis(damage_type, confidence)
|
156 |
-
|
157 |
st.markdown("### 🔍 Enhanced Analysis")
|
158 |
|
159 |
with st.expander("📚 Technical Details", expanded=True):
|
160 |
for detail in enhanced_info["technical_details"]:
|
161 |
st.markdown(detail)
|
162 |
-
|
163 |
with st.expander("⚠️ Safety Considerations"):
|
164 |
for safety in enhanced_info["safety_considerations"]:
|
165 |
st.warning(safety)
|
166 |
-
|
167 |
with st.expander("👷 Expert Recommendations"):
|
168 |
for rec in enhanced_info["expert_recommendations"]:
|
169 |
st.info(rec)
|
170 |
-
|
171 |
-
custom_query = st.text_input(
|
172 |
-
"Ask specific questions about this damage type:",
|
173 |
-
placeholder="E.g., What are the long-term implications of this damage?",
|
174 |
-
key=f"text_input_{damage_type}"
|
175 |
-
)
|
176 |
-
if custom_query:
|
177 |
-
custom_results = RAG_SYSTEM.get_enhanced_analysis(
|
178 |
-
damage_type,
|
179 |
-
confidence,
|
180 |
-
custom_query=custom_query
|
181 |
-
)
|
182 |
-
st.markdown("### 💡 Custom Query Results")
|
183 |
-
for category, results in custom_results.items():
|
184 |
-
if results:
|
185 |
-
st.markdown(f"**{category.replace('_', ' ').title()}:**")
|
186 |
-
for result in results:
|
187 |
-
st.markdown(result)
|
188 |
except Exception as e:
|
189 |
-
|
190 |
-
|
191 |
-
def display_analysis_results(predictions, analysis_time):
|
192 |
-
"""Display analysis results with damage details"""
|
193 |
-
st.markdown("### 📊 Analysis Results")
|
194 |
-
st.markdown(f"*Analysis completed in {analysis_time:.2f} seconds*")
|
195 |
-
|
196 |
-
detected = False
|
197 |
-
for idx, prob in enumerate(predictions):
|
198 |
-
confidence = float(prob) * 100
|
199 |
-
if confidence > 15:
|
200 |
-
detected = True
|
201 |
-
damage_type = DAMAGE_TYPES[idx]['name']
|
202 |
-
cases = KNOWLEDGE_BASE[damage_type]
|
203 |
-
|
204 |
-
with st.expander(f"{damage_type.replace('_', ' ').title()} - {confidence:.1f}%", expanded=True):
|
205 |
-
st.markdown(
|
206 |
-
f"""
|
207 |
-
<style>
|
208 |
-
.stProgress > div > div > div > div {{
|
209 |
-
background-color: {DAMAGE_TYPES[idx]['color']} !important;
|
210 |
-
}}
|
211 |
-
</style>
|
212 |
-
""",
|
213 |
-
unsafe_allow_html=True
|
214 |
-
)
|
215 |
-
st.progress(confidence / 100)
|
216 |
-
|
217 |
-
tabs = st.tabs(["📋 Details", "🔧 Repairs", "⚠️ Actions"])
|
218 |
-
|
219 |
-
with tabs[0]:
|
220 |
-
for case in cases:
|
221 |
-
st.markdown(f"""
|
222 |
-
- **Severity:** {case['severity']}
|
223 |
-
- **Description:** {case['description']}
|
224 |
-
- **Location:** {case['location']}
|
225 |
-
- **Required Expertise:** {case['required_expertise']}
|
226 |
-
""")
|
227 |
-
|
228 |
-
with tabs[1]:
|
229 |
-
for step in cases[0]['repair_method']:
|
230 |
-
st.markdown(f"✓ {step}")
|
231 |
-
st.info(f"**Estimated Cost:** {cases[0]['estimated_cost']}")
|
232 |
-
st.info(f"**Timeframe:** {cases[0]['timeframe']}")
|
233 |
-
|
234 |
-
with tabs[2]:
|
235 |
-
st.warning("**Immediate Actions Required:**")
|
236 |
-
st.markdown(cases[0]['immediate_action'])
|
237 |
-
st.success("**Prevention Measures:**")
|
238 |
-
st.markdown(cases[0]['prevention'])
|
239 |
-
|
240 |
-
# Display enhanced analysis
|
241 |
-
display_enhanced_analysis(damage_type, confidence)
|
242 |
-
|
243 |
-
if not detected:
|
244 |
-
st.info("No significant structural damage detected. Regular maintenance recommended.")
|
245 |
|
|
|
246 |
def main():
|
247 |
-
"""Main application function"""
|
248 |
-
init_session_state()
|
249 |
st.set_page_config(
|
250 |
page_title="Structural Damage Analyzer Pro",
|
251 |
page_icon="🏗️",
|
252 |
-
layout="wide"
|
253 |
-
initial_sidebar_state="expanded"
|
254 |
)
|
255 |
-
|
256 |
-
st.markdown(get_custom_css(), unsafe_allow_html=True)
|
257 |
-
|
258 |
-
# Sidebar
|
259 |
-
with st.sidebar:
|
260 |
-
st.markdown("### ⚙️ Settings")
|
261 |
-
st.session_state.dark_mode = st.toggle("Dark Mode", st.session_state.dark_mode)
|
262 |
-
st.markdown("### 📖 Analysis History")
|
263 |
-
if st.session_state.history:
|
264 |
-
for item in st.session_state.history[-5:]:
|
265 |
-
st.markdown(f"- {item}")
|
266 |
-
|
267 |
-
display_header()
|
268 |
|
269 |
# Load model and initialize RAG system
|
270 |
global MODEL, PROCESSOR
|
271 |
if MODEL is None or PROCESSOR is None:
|
272 |
-
|
273 |
-
MODEL, PROCESSOR = load_model()
|
274 |
-
if MODEL is None:
|
275 |
-
st.error("Failed to load model. Please refresh the page.")
|
276 |
-
return
|
277 |
-
|
278 |
init_rag_system()
|
279 |
|
280 |
-
# File upload
|
281 |
uploaded_file = st.file_uploader(
|
282 |
-
"
|
283 |
-
type=['jpg', 'jpeg', 'png']
|
284 |
-
help="Supported formats: JPG, JPEG, PNG"
|
285 |
)
|
286 |
|
287 |
if uploaded_file:
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
return
|
292 |
-
|
293 |
-
image = preprocess_image(uploaded_file)
|
294 |
-
if image is None:
|
295 |
-
return
|
296 |
-
|
297 |
-
validate_image(image)
|
298 |
-
|
299 |
-
col1, col2 = st.columns([1, 1])
|
300 |
-
|
301 |
-
with col1:
|
302 |
-
st.image(image, caption="Uploaded Structure", use_container_width=True)
|
303 |
-
|
304 |
-
with col2:
|
305 |
-
with st.spinner("🔍 Analyzing damage..."):
|
306 |
-
start_time = time.time()
|
307 |
-
predictions = analyze_damage(image, MODEL, PROCESSOR)
|
308 |
-
analysis_time = time.time() - start_time
|
309 |
-
|
310 |
-
if predictions is not None:
|
311 |
-
display_analysis_results(predictions, analysis_time)
|
312 |
-
st.session_state.history.append(f"Analyzed image: {uploaded_file.name}")
|
313 |
|
314 |
-
|
315 |
-
|
316 |
-
st.error(f"Error processing image: {str(e)}")
|
317 |
-
st.info("Please try uploading a different image.")
|
318 |
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
|
331 |
if __name__ == "__main__":
|
332 |
-
main()
|
|
|
4 |
import torch
|
5 |
import time
|
6 |
import gc
|
7 |
+
import logging
|
8 |
from knowledge_base import KNOWLEDGE_BASE, DAMAGE_TYPES
|
9 |
from rag_utils import RAGSystem
|
10 |
|
11 |
+
# Configure logging
|
12 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
13 |
+
|
14 |
# Constants
|
15 |
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
|
16 |
MAX_IMAGE_SIZE = 1024 # Maximum dimension for images
|
|
|
20 |
PROCESSOR = None
|
21 |
RAG_SYSTEM = None
|
22 |
|
23 |
+
# Cleanup function for memory
|
24 |
def cleanup_memory():
|
25 |
"""Clean up memory and GPU cache"""
|
26 |
gc.collect()
|
27 |
if torch.cuda.is_available():
|
28 |
torch.cuda.empty_cache()
|
29 |
|
30 |
+
# Session state initialization
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
@st.cache_resource(show_spinner="Loading AI model...")
|
32 |
def load_model():
|
33 |
"""Load and cache the model and processor"""
|
34 |
try:
|
35 |
model_name = "google/vit-base-patch16-224"
|
|
|
36 |
processor = ViTImageProcessor.from_pretrained(model_name)
|
|
|
|
|
37 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
38 |
+
|
39 |
model = ViTForImageClassification.from_pretrained(
|
40 |
model_name,
|
41 |
num_labels=len(DAMAGE_TYPES),
|
42 |
ignore_mismatched_sizes=True,
|
43 |
).to(device)
|
44 |
+
|
|
|
45 |
model.eval()
|
46 |
+
logging.info("Model loaded successfully.")
|
47 |
return model, processor
|
48 |
except Exception as e:
|
49 |
+
logging.error(f"Failed to load model: {str(e)}")
|
50 |
+
st.error("Error loading model. Please restart the app.")
|
51 |
return None, None
|
52 |
|
53 |
+
# Initialize RAG system
|
54 |
+
@st.cache_resource
|
55 |
def init_rag_system():
|
|
|
56 |
global RAG_SYSTEM
|
57 |
+
try:
|
58 |
RAG_SYSTEM = RAGSystem()
|
59 |
RAG_SYSTEM.initialize_knowledge_base(KNOWLEDGE_BASE)
|
60 |
+
logging.info("RAG system initialized successfully.")
|
61 |
+
except Exception as e:
|
62 |
+
logging.error(f"Failed to initialize RAG system: {str(e)}")
|
63 |
+
st.error("Error initializing knowledge base.")
|
64 |
|
65 |
+
# Image validation
|
66 |
def validate_image(image):
|
|
|
67 |
if image.size[0] * image.size[1] > 1024 * 1024:
|
68 |
+
st.warning("Large image detected. Resizing for better performance.")
|
69 |
if image.format not in ['JPEG', 'PNG']:
|
70 |
+
st.warning("Non-optimal image format. Use JPEG or PNG.")
|
71 |
|
72 |
+
# Image preprocessing
|
73 |
def preprocess_image(uploaded_file):
|
|
|
74 |
try:
|
75 |
image = Image.open(uploaded_file)
|
|
|
76 |
if max(image.size) > MAX_IMAGE_SIZE:
|
77 |
ratio = MAX_IMAGE_SIZE / max(image.size)
|
78 |
new_size = tuple([int(dim * ratio) for dim in image.size])
|
79 |
image = image.resize(new_size, Image.Resampling.LANCZOS)
|
80 |
return image
|
81 |
except Exception as e:
|
82 |
+
logging.error(f"Error processing image: {str(e)}")
|
83 |
+
st.error("Image processing error.")
|
84 |
return None
|
85 |
|
86 |
+
# Damage analysis
|
87 |
def analyze_damage(image, model, processor):
|
|
|
88 |
try:
|
89 |
device = next(model.parameters()).device
|
90 |
with torch.no_grad():
|
91 |
image = image.convert('RGB')
|
92 |
inputs = processor(images=image, return_tensors="pt")
|
|
|
93 |
inputs = {k: v.to(device) for k, v in inputs.items()}
|
94 |
outputs = model(**inputs)
|
95 |
probs = torch.nn.functional.softmax(outputs.logits, dim=1)[0]
|
96 |
cleanup_memory()
|
97 |
+
return probs.cpu()
|
98 |
+
except Exception as e:
|
99 |
+
logging.error(f"Error analyzing image: {str(e)}")
|
100 |
+
st.error("Image analysis failed.")
|
|
|
|
|
|
|
101 |
return None
|
102 |
|
103 |
+
# Display enhanced analysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
def display_enhanced_analysis(damage_type, confidence):
|
|
|
105 |
try:
|
106 |
enhanced_info = RAG_SYSTEM.get_enhanced_analysis(damage_type, confidence)
|
|
|
107 |
st.markdown("### 🔍 Enhanced Analysis")
|
108 |
|
109 |
with st.expander("📚 Technical Details", expanded=True):
|
110 |
for detail in enhanced_info["technical_details"]:
|
111 |
st.markdown(detail)
|
112 |
+
|
113 |
with st.expander("⚠️ Safety Considerations"):
|
114 |
for safety in enhanced_info["safety_considerations"]:
|
115 |
st.warning(safety)
|
116 |
+
|
117 |
with st.expander("👷 Expert Recommendations"):
|
118 |
for rec in enhanced_info["expert_recommendations"]:
|
119 |
st.info(rec)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
except Exception as e:
|
121 |
+
logging.error(f"Failed to generate enhanced analysis: {str(e)}")
|
122 |
+
st.error("Error generating enhanced analysis.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
+
# Main function
|
125 |
def main():
|
|
|
|
|
126 |
st.set_page_config(
|
127 |
page_title="Structural Damage Analyzer Pro",
|
128 |
page_icon="🏗️",
|
129 |
+
layout="wide"
|
|
|
130 |
)
|
131 |
+
st.title("🏗️ Structural Damage Analyzer Pro")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
# Load model and initialize RAG system
|
134 |
global MODEL, PROCESSOR
|
135 |
if MODEL is None or PROCESSOR is None:
|
136 |
+
MODEL, PROCESSOR = load_model()
|
|
|
|
|
|
|
|
|
|
|
137 |
init_rag_system()
|
138 |
|
|
|
139 |
uploaded_file = st.file_uploader(
|
140 |
+
"Upload an image for analysis (JPG, PNG)",
|
141 |
+
type=['jpg', 'jpeg', 'png']
|
|
|
142 |
)
|
143 |
|
144 |
if uploaded_file:
|
145 |
+
if uploaded_file.size > MAX_FILE_SIZE:
|
146 |
+
st.error("File too large. Limit: 5MB.")
|
147 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
+
image = preprocess_image(uploaded_file)
|
150 |
+
validate_image(image)
|
|
|
|
|
151 |
|
152 |
+
st.image(image, caption="Uploaded Image", use_column_width=True)
|
153 |
+
|
154 |
+
with st.spinner("Analyzing damage..."):
|
155 |
+
start_time = time.time()
|
156 |
+
predictions = analyze_damage(image, MODEL, PROCESSOR)
|
157 |
+
analysis_time = time.time() - start_time
|
158 |
+
|
159 |
+
if predictions is not None:
|
160 |
+
st.markdown(f"*Analysis completed in {analysis_time:.2f} seconds*")
|
161 |
+
confidence = float(predictions[0]) * 100
|
162 |
+
display_enhanced_analysis(DAMAGE_TYPES[0]['name'], confidence)
|
163 |
|
164 |
if __name__ == "__main__":
|
165 |
+
main()
|