Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -162,11 +162,11 @@ st.markdown("""
|
|
162 |
</style>
|
163 |
""", unsafe_allow_html=True)
|
164 |
|
165 |
-
def analyze_image(image, analysis_types):
|
166 |
-
"""Analyze image with selected analysis types"""
|
167 |
# Convert uploaded image to bytes
|
168 |
if image is None:
|
169 |
-
return None, {}, {}, ""
|
170 |
|
171 |
img_byte_arr = io.BytesIO()
|
172 |
image.save(img_byte_arr, format='PNG')
|
@@ -179,22 +179,42 @@ def analyze_image(image, analysis_types):
|
|
179 |
labels_data = {}
|
180 |
objects_data = {}
|
181 |
text_content = ""
|
|
|
|
|
182 |
|
183 |
img_with_boxes = image.copy()
|
184 |
draw = ImageDraw.Draw(img_with_boxes)
|
185 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
if "Labels" in analysis_types:
|
187 |
labels = client.label_detection(image=vision_image)
|
|
|
188 |
labels_data = {label.description: round(label.score * 100)
|
189 |
-
for label in labels.label_annotations
|
|
|
190 |
|
191 |
if "Objects" in analysis_types:
|
192 |
objects = client.object_localization(image=vision_image)
|
|
|
|
|
|
|
|
|
193 |
objects_data = {obj.name: round(obj.score * 100)
|
194 |
-
for obj in
|
195 |
|
196 |
# Draw object boundaries
|
197 |
-
for obj in
|
198 |
box = [(vertex.x * image.width, vertex.y * image.height)
|
199 |
for vertex in obj.bounding_poly.normalized_vertices]
|
200 |
draw.polygon(box, outline='red', width=2)
|
@@ -207,6 +227,23 @@ def analyze_image(image, analysis_types):
|
|
207 |
if text.text_annotations:
|
208 |
text_content = text.text_annotations[0].description
|
209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
# Draw text boundaries
|
211 |
for text_annot in text.text_annotations[1:]: # Skip the first one (full text)
|
212 |
box = [(vertex.x, vertex.y) for vertex in text_annot.bounding_poly.vertices]
|
@@ -214,7 +251,11 @@ def analyze_image(image, analysis_types):
|
|
214 |
|
215 |
if "Face Detection" in analysis_types:
|
216 |
faces = client.face_detection(image=vision_image)
|
217 |
-
|
|
|
|
|
|
|
|
|
218 |
vertices = face.bounding_poly.vertices
|
219 |
box = [(vertex.x, vertex.y) for vertex in vertices]
|
220 |
draw.polygon(box, outline='green', width=2)
|
@@ -225,15 +266,18 @@ def analyze_image(image, analysis_types):
|
|
225 |
py = landmark.position.y
|
226 |
draw.ellipse((px-2, py-2, px+2, py+2), fill='yellow')
|
227 |
|
228 |
-
|
|
|
229 |
|
230 |
-
def display_results(annotated_img, labels, objects, text):
|
231 |
-
"""Display analysis results in a clean format"""
|
232 |
# Store results in session state for chatbot context
|
233 |
st.session_state.analysis_results = {
|
234 |
"labels": labels,
|
235 |
"objects": objects,
|
236 |
"text": text,
|
|
|
|
|
237 |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
|
238 |
}
|
239 |
|
@@ -268,12 +312,37 @@ def display_results(annotated_img, labels, objects, text):
|
|
268 |
# Text tab
|
269 |
if text:
|
270 |
st.markdown("##### 📝 Text Detected")
|
|
|
|
|
271 |
st.markdown('<div class="result-container">', unsafe_allow_html=True)
|
272 |
st.markdown(f'<div class="text-item">{text}</div>', unsafe_allow_html=True)
|
273 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
|
275 |
# Add Download Summary Image button
|
276 |
-
summary_img = create_summary_image(annotated_img, labels, objects, text)
|
277 |
buf = io.BytesIO()
|
278 |
summary_img.save(buf, format="JPEG", quality=90)
|
279 |
byte_im = buf.getvalue()
|
@@ -286,7 +355,7 @@ def display_results(annotated_img, labels, objects, text):
|
|
286 |
help="Download a complete image showing the analyzed image and all detected features"
|
287 |
)
|
288 |
|
289 |
-
def create_summary_image(annotated_img, labels, objects, text):
|
290 |
"""Create a downloadable summary image with analysis results"""
|
291 |
# Create a new image with space for results
|
292 |
img_width, img_height = annotated_img.size
|
@@ -690,7 +759,7 @@ def list_bigquery_resources():
|
|
690 |
return resources
|
691 |
|
692 |
def process_video_file(video_file, analysis_types):
|
693 |
-
"""Process an uploaded video file with enhanced Vision AI detection and
|
694 |
# Create a temporary file to save the uploaded video
|
695 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
|
696 |
temp_file.write(video_file.read())
|
@@ -743,14 +812,22 @@ def process_video_file(video_file, analysis_types):
|
|
743 |
progress_bar = st.progress(0)
|
744 |
status_text = st.empty()
|
745 |
|
746 |
-
#
|
747 |
detection_stats = {
|
748 |
"objects": {},
|
749 |
"faces": 0,
|
750 |
"text_blocks": 0,
|
751 |
-
"labels": {}
|
|
|
|
|
|
|
|
|
752 |
}
|
753 |
|
|
|
|
|
|
|
|
|
754 |
try:
|
755 |
frame_count = 0
|
756 |
while frame_count < max_frames: # Limit to 10 seconds
|
@@ -769,7 +846,26 @@ def process_video_file(video_file, analysis_types):
|
|
769 |
cv2.putText(frame, f"Time: {frame_count/fps:.2f}s",
|
770 |
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
771 |
|
772 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
773 |
if frame_count % process_every_n_frames == 0:
|
774 |
try:
|
775 |
# Convert OpenCV frame to PIL Image for Vision API
|
@@ -786,12 +882,28 @@ def process_video_file(video_file, analysis_types):
|
|
786 |
objects = client.object_localization(image=vision_image)
|
787 |
# Draw boxes around detected objects with enhanced info
|
788 |
for obj in objects.localized_object_annotations:
|
789 |
-
|
790 |
-
|
791 |
-
|
|
|
792 |
else:
|
793 |
-
detection_stats["objects"][
|
794 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
795 |
# Calculate box coordinates
|
796 |
box = [(vertex.x * frame.shape[1], vertex.y * frame.shape[0])
|
797 |
for vertex in obj.bounding_poly.normalized_vertices]
|
@@ -932,7 +1044,15 @@ def process_video_file(video_file, analysis_types):
|
|
932 |
os.unlink(temp_video_path)
|
933 |
os.unlink(output_path)
|
934 |
|
935 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
936 |
results = {"detection_stats": detection_stats}
|
937 |
|
938 |
# Store results in session state for chatbot context
|
@@ -1457,12 +1577,28 @@ def main():
|
|
1457 |
if st.checkbox("Face Detection"):
|
1458 |
analysis_types.append("Face Detection")
|
1459 |
|
|
|
|
|
|
|
|
|
1460 |
st.markdown("---")
|
1461 |
|
|
|
|
|
|
|
|
|
|
|
1462 |
# Image quality settings
|
1463 |
st.write("Image settings:")
|
1464 |
quality = st.slider("Image Quality", min_value=0, max_value=100, value=100)
|
1465 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1466 |
st.markdown("---")
|
1467 |
st.info("This application analyzes images using Google Cloud Vision AI. Upload an image to get started.")
|
1468 |
|
@@ -1492,10 +1628,10 @@ def main():
|
|
1492 |
else:
|
1493 |
with st.spinner("Analyzing image..."):
|
1494 |
# Call analyze function
|
1495 |
-
annotated_img, labels, objects, text = analyze_image(image, analysis_types)
|
1496 |
|
1497 |
# Display results
|
1498 |
-
display_results(annotated_img, labels, objects, text)
|
1499 |
|
1500 |
# Add download button for the annotated image
|
1501 |
buf = io.BytesIO()
|
@@ -1515,118 +1651,78 @@ def main():
|
|
1515 |
|
1516 |
uploaded_files = st.file_uploader("Choose images...", type=["jpg", "jpeg", "png"], accept_multiple_files=True)
|
1517 |
|
1518 |
-
if uploaded_files:
|
1519 |
-
# Limit to 5 images
|
1520 |
if len(uploaded_files) > 5:
|
1521 |
-
st.warning("
|
1522 |
uploaded_files = uploaded_files[:5]
|
1523 |
|
1524 |
-
|
1525 |
-
|
1526 |
-
|
1527 |
-
|
1528 |
-
|
1529 |
-
|
1530 |
-
st.image(image, caption=f"Image {i+1}", use_container_width=True)
|
1531 |
-
|
1532 |
-
# Add analyze button for batch processing
|
1533 |
-
if st.button("Analyze All Images"):
|
1534 |
-
if not analysis_types:
|
1535 |
-
st.warning("Please select at least one analysis type.")
|
1536 |
-
else:
|
1537 |
-
# Initialize containers for batch summary
|
1538 |
-
all_labels = {}
|
1539 |
-
all_objects = {}
|
1540 |
|
1541 |
-
#
|
1542 |
-
|
1543 |
-
|
1544 |
-
|
1545 |
-
image
|
1546 |
-
|
1547 |
-
|
1548 |
-
|
1549 |
-
|
1550 |
-
|
1551 |
-
|
1552 |
-
|
1553 |
-
|
1554 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1555 |
|
1556 |
-
|
1557 |
-
|
1558 |
-
|
1559 |
-
|
1560 |
-
else:
|
1561 |
-
all_labels[label] = confidence
|
1562 |
|
1563 |
-
|
1564 |
-
|
1565 |
-
|
1566 |
-
|
1567 |
-
|
1568 |
|
1569 |
-
|
1570 |
-
|
1571 |
-
|
1572 |
-
|
1573 |
-
|
1574 |
-
|
1575 |
-
buf = io.BytesIO()
|
1576 |
-
annotated_img.save(buf, format="PNG")
|
1577 |
-
byte_im = buf.getvalue()
|
1578 |
-
|
1579 |
-
st.download_button(
|
1580 |
-
label=f"Download Annotated Image {i+1}",
|
1581 |
-
data=byte_im,
|
1582 |
-
file_name=f"annotated_image_{i+1}.png",
|
1583 |
-
mime="image/png"
|
1584 |
-
)
|
1585 |
-
|
1586 |
-
# Display batch summary
|
1587 |
-
st.markdown('<div class="subheader">Batch Analysis Summary</div>', unsafe_allow_html=True)
|
1588 |
-
|
1589 |
-
col1, col2 = st.columns(2)
|
1590 |
-
|
1591 |
-
with col1:
|
1592 |
-
if all_labels:
|
1593 |
-
st.markdown("#### Common Labels Across Images")
|
1594 |
-
# Sort by confidence
|
1595 |
-
sorted_labels = dict(sorted(all_labels.items(), key=lambda x: x[1], reverse=True))
|
1596 |
-
for label, confidence in sorted_labels.items():
|
1597 |
-
st.markdown(f'<div class="label-item">{label}: {confidence}%</div>', unsafe_allow_html=True)
|
1598 |
|
1599 |
-
|
1600 |
-
|
1601 |
-
|
1602 |
-
|
1603 |
-
|
1604 |
-
for obj, confidence in sorted_objects.items():
|
1605 |
-
st.markdown(f'<div class="object-item">{obj}: {confidence}%</div>', unsafe_allow_html=True)
|
1606 |
-
|
1607 |
-
# Create visualization for batch summary if there are labels or objects
|
1608 |
-
if all_labels or all_objects:
|
1609 |
-
st.markdown("#### Visual Summary")
|
1610 |
|
1611 |
-
#
|
1612 |
-
|
1613 |
-
|
1614 |
-
|
1615 |
-
|
1616 |
-
|
1617 |
-
|
1618 |
-
|
1619 |
-
st.plotly_chart(fig_labels)
|
1620 |
|
1621 |
-
#
|
1622 |
-
|
1623 |
-
|
1624 |
-
|
1625 |
-
y=list(all_objects.values()),
|
1626 |
-
labels={'x': 'Object', 'y': 'Confidence (%)'},
|
1627 |
-
title='Top Objects Across All Images'
|
1628 |
-
)
|
1629 |
-
st.plotly_chart(fig_objects)
|
1630 |
|
1631 |
elif selected == "Video Analysis":
|
1632 |
st.markdown('<div class="subheader">Video Analysis</div>', unsafe_allow_html=True)
|
|
|
162 |
</style>
|
163 |
""", unsafe_allow_html=True)
|
164 |
|
165 |
+
def analyze_image(image, analysis_types, confidence_threshold=0.5):
|
166 |
+
"""Analyze image with selected analysis types and confidence filtering"""
|
167 |
# Convert uploaded image to bytes
|
168 |
if image is None:
|
169 |
+
return None, {}, {}, "", {}
|
170 |
|
171 |
img_byte_arr = io.BytesIO()
|
172 |
image.save(img_byte_arr, format='PNG')
|
|
|
179 |
labels_data = {}
|
180 |
objects_data = {}
|
181 |
text_content = ""
|
182 |
+
colors_data = {} # New: store dominant colors
|
183 |
+
text_language = "" # New: store detected language
|
184 |
|
185 |
img_with_boxes = image.copy()
|
186 |
draw = ImageDraw.Draw(img_with_boxes)
|
187 |
|
188 |
+
# Extract color information regardless of analysis types
|
189 |
+
if "Visual Attributes" in analysis_types:
|
190 |
+
image_properties = client.image_properties(image=vision_image).image_properties_annotation
|
191 |
+
# Get top 5 dominant colors with scores
|
192 |
+
colors_data = {
|
193 |
+
f"Color #{i+1}": {
|
194 |
+
"rgb": (int(color.color.red), int(color.color.green), int(color.color.blue)),
|
195 |
+
"score": round(color.score * 100, 2),
|
196 |
+
"pixel_fraction": round(color.pixel_fraction * 100, 2)
|
197 |
+
} for i, color in enumerate(image_properties.dominant_colors.colors[:5])
|
198 |
+
}
|
199 |
+
|
200 |
if "Labels" in analysis_types:
|
201 |
labels = client.label_detection(image=vision_image)
|
202 |
+
# Apply confidence threshold
|
203 |
labels_data = {label.description: round(label.score * 100)
|
204 |
+
for label in labels.label_annotations
|
205 |
+
if label.score >= confidence_threshold}
|
206 |
|
207 |
if "Objects" in analysis_types:
|
208 |
objects = client.object_localization(image=vision_image)
|
209 |
+
# Apply confidence threshold
|
210 |
+
filtered_objects = [obj for obj in objects.localized_object_annotations
|
211 |
+
if obj.score >= confidence_threshold]
|
212 |
+
|
213 |
objects_data = {obj.name: round(obj.score * 100)
|
214 |
+
for obj in filtered_objects}
|
215 |
|
216 |
# Draw object boundaries
|
217 |
+
for obj in filtered_objects:
|
218 |
box = [(vertex.x * image.width, vertex.y * image.height)
|
219 |
for vertex in obj.bounding_poly.normalized_vertices]
|
220 |
draw.polygon(box, outline='red', width=2)
|
|
|
227 |
if text.text_annotations:
|
228 |
text_content = text.text_annotations[0].description
|
229 |
|
230 |
+
# New: Detect language if text is found
|
231 |
+
if text_content:
|
232 |
+
try:
|
233 |
+
# Get language of text
|
234 |
+
document = vision.types.Document(
|
235 |
+
content=content,
|
236 |
+
type_=vision.types.Document.Type.GENERAL_DOCUMENT
|
237 |
+
)
|
238 |
+
response = client.document_text_detection(image=vision_image)
|
239 |
+
if response.text_annotations:
|
240 |
+
# Get the language code from the first page
|
241 |
+
if response.pages and response.pages[0].property.detected_languages:
|
242 |
+
lang = response.pages[0].property.detected_languages[0]
|
243 |
+
text_language = f"{lang.language_code} ({round(lang.confidence * 100)}%)"
|
244 |
+
except Exception as e:
|
245 |
+
text_language = "Detection failed"
|
246 |
+
|
247 |
# Draw text boundaries
|
248 |
for text_annot in text.text_annotations[1:]: # Skip the first one (full text)
|
249 |
box = [(vertex.x, vertex.y) for vertex in text_annot.bounding_poly.vertices]
|
|
|
251 |
|
252 |
if "Face Detection" in analysis_types:
|
253 |
faces = client.face_detection(image=vision_image)
|
254 |
+
# Apply confidence threshold - filter by detection confidence
|
255 |
+
filtered_faces = [face for face in faces.face_annotations
|
256 |
+
if face.detection_confidence >= confidence_threshold]
|
257 |
+
|
258 |
+
for face in filtered_faces:
|
259 |
vertices = face.bounding_poly.vertices
|
260 |
box = [(vertex.x, vertex.y) for vertex in vertices]
|
261 |
draw.polygon(box, outline='green', width=2)
|
|
|
266 |
py = landmark.position.y
|
267 |
draw.ellipse((px-2, py-2, px+2, py+2), fill='yellow')
|
268 |
|
269 |
+
# Return extended results
|
270 |
+
return img_with_boxes, labels_data, objects_data, text_content, colors_data, text_language
|
271 |
|
272 |
+
def display_results(annotated_img, labels, objects, text, colors=None, text_language=None):
|
273 |
+
"""Display analysis results in a clean format with enhanced features"""
|
274 |
# Store results in session state for chatbot context
|
275 |
st.session_state.analysis_results = {
|
276 |
"labels": labels,
|
277 |
"objects": objects,
|
278 |
"text": text,
|
279 |
+
"colors": colors if colors else {},
|
280 |
+
"text_language": text_language if text_language else "",
|
281 |
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
|
282 |
}
|
283 |
|
|
|
312 |
# Text tab
|
313 |
if text:
|
314 |
st.markdown("##### 📝 Text Detected")
|
315 |
+
if text_language:
|
316 |
+
st.markdown(f"**Detected Language:** {text_language}")
|
317 |
st.markdown('<div class="result-container">', unsafe_allow_html=True)
|
318 |
st.markdown(f'<div class="text-item">{text}</div>', unsafe_allow_html=True)
|
319 |
st.markdown('</div>', unsafe_allow_html=True)
|
320 |
+
|
321 |
+
# Color analysis tab (new)
|
322 |
+
if colors:
|
323 |
+
st.markdown("##### 🎨 Dominant Colors")
|
324 |
+
st.markdown('<div class="result-container">', unsafe_allow_html=True)
|
325 |
+
|
326 |
+
# Create color swatches
|
327 |
+
for color_name, color_data in colors.items():
|
328 |
+
rgb = color_data["rgb"]
|
329 |
+
hex_color = f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
|
330 |
+
|
331 |
+
# Display color swatch with info
|
332 |
+
st.markdown(f"""
|
333 |
+
<div style="display:flex; align-items:center; margin-bottom:10px;">
|
334 |
+
<div style="background-color:{hex_color}; width:50px; height:30px; margin-right:15px; border:1px solid #ddd;"></div>
|
335 |
+
<div>
|
336 |
+
<strong>{color_name}</strong>: {color_data["score"]}% coverage<br>
|
337 |
+
RGB: {rgb}
|
338 |
+
</div>
|
339 |
+
</div>
|
340 |
+
""", unsafe_allow_html=True)
|
341 |
+
|
342 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
343 |
|
344 |
# Add Download Summary Image button
|
345 |
+
summary_img = create_summary_image(annotated_img, labels, objects, text, colors)
|
346 |
buf = io.BytesIO()
|
347 |
summary_img.save(buf, format="JPEG", quality=90)
|
348 |
byte_im = buf.getvalue()
|
|
|
355 |
help="Download a complete image showing the analyzed image and all detected features"
|
356 |
)
|
357 |
|
358 |
+
def create_summary_image(annotated_img, labels, objects, text, colors=None):
|
359 |
"""Create a downloadable summary image with analysis results"""
|
360 |
# Create a new image with space for results
|
361 |
img_width, img_height = annotated_img.size
|
|
|
759 |
return resources
|
760 |
|
761 |
def process_video_file(video_file, analysis_types):
|
762 |
+
"""Process an uploaded video file with enhanced Vision AI detection and analytics"""
|
763 |
# Create a temporary file to save the uploaded video
|
764 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
|
765 |
temp_file.write(video_file.read())
|
|
|
812 |
progress_bar = st.progress(0)
|
813 |
status_text = st.empty()
|
814 |
|
815 |
+
# Enhanced statistics tracking
|
816 |
detection_stats = {
|
817 |
"objects": {},
|
818 |
"faces": 0,
|
819 |
"text_blocks": 0,
|
820 |
+
"labels": {},
|
821 |
+
# New advanced tracking
|
822 |
+
"object_tracking": {}, # Track object appearances by frame
|
823 |
+
"activity_metrics": [], # Track frame-to-frame differences
|
824 |
+
"scene_changes": [] # Track major scene transitions
|
825 |
}
|
826 |
|
827 |
+
# For scene change detection
|
828 |
+
previous_frame_gray = None
|
829 |
+
scene_change_threshold = 40.0 # Threshold for scene change detection
|
830 |
+
|
831 |
try:
|
832 |
frame_count = 0
|
833 |
while frame_count < max_frames: # Limit to 10 seconds
|
|
|
846 |
cv2.putText(frame, f"Time: {frame_count/fps:.2f}s",
|
847 |
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
848 |
|
849 |
+
# Activity detection and scene change detection
|
850 |
+
current_frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
851 |
+
current_frame_gray = cv2.GaussianBlur(current_frame_gray, (21, 21), 0)
|
852 |
+
|
853 |
+
if previous_frame_gray is not None:
|
854 |
+
# Calculate frame difference for activity detection
|
855 |
+
frame_diff = cv2.absdiff(current_frame_gray, previous_frame_gray)
|
856 |
+
activity_level = np.mean(frame_diff)
|
857 |
+
detection_stats["activity_metrics"].append((frame_count/fps, activity_level))
|
858 |
+
|
859 |
+
# Scene change detection
|
860 |
+
if activity_level > scene_change_threshold:
|
861 |
+
detection_stats["scene_changes"].append(frame_count/fps)
|
862 |
+
# Mark scene change on frame
|
863 |
+
cv2.putText(frame, "SCENE CHANGE",
|
864 |
+
(width // 2 - 100, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2)
|
865 |
+
|
866 |
+
previous_frame_gray = current_frame_gray
|
867 |
+
|
868 |
+
# Process frames with Vision API
|
869 |
if frame_count % process_every_n_frames == 0:
|
870 |
try:
|
871 |
# Convert OpenCV frame to PIL Image for Vision API
|
|
|
882 |
objects = client.object_localization(image=vision_image)
|
883 |
# Draw boxes around detected objects with enhanced info
|
884 |
for obj in objects.localized_object_annotations:
|
885 |
+
obj_name = obj.name
|
886 |
+
# Update basic stats
|
887 |
+
if obj_name in detection_stats["objects"]:
|
888 |
+
detection_stats["objects"][obj_name] += 1
|
889 |
else:
|
890 |
+
detection_stats["objects"][obj_name] = 1
|
891 |
|
892 |
+
# Enhanced object tracking
|
893 |
+
timestamp = frame_count/fps
|
894 |
+
if obj_name not in detection_stats["object_tracking"]:
|
895 |
+
detection_stats["object_tracking"][obj_name] = {
|
896 |
+
"first_seen": timestamp,
|
897 |
+
"last_seen": timestamp,
|
898 |
+
"frames_present": 1,
|
899 |
+
"timestamps": [timestamp]
|
900 |
+
}
|
901 |
+
else:
|
902 |
+
tracking = detection_stats["object_tracking"][obj_name]
|
903 |
+
tracking["frames_present"] += 1
|
904 |
+
tracking["last_seen"] = timestamp
|
905 |
+
tracking["timestamps"].append(timestamp)
|
906 |
+
|
907 |
# Calculate box coordinates
|
908 |
box = [(vertex.x * frame.shape[1], vertex.y * frame.shape[0])
|
909 |
for vertex in obj.bounding_poly.normalized_vertices]
|
|
|
1044 |
os.unlink(temp_video_path)
|
1045 |
os.unlink(output_path)
|
1046 |
|
1047 |
+
# Calculate additional statistics
|
1048 |
+
for obj_name, tracking in detection_stats["object_tracking"].items():
|
1049 |
+
# Calculate total screen time
|
1050 |
+
tracking["screen_time"] = round(tracking["frames_present"] * (1/fps) * process_every_n_frames, 2)
|
1051 |
+
# Calculate average confidence if available
|
1052 |
+
if "confidences" in tracking and tracking["confidences"]:
|
1053 |
+
tracking["avg_confidence"] = sum(tracking["confidences"]) / len(tracking["confidences"])
|
1054 |
+
|
1055 |
+
# Return enhanced results
|
1056 |
results = {"detection_stats": detection_stats}
|
1057 |
|
1058 |
# Store results in session state for chatbot context
|
|
|
1577 |
if st.checkbox("Face Detection"):
|
1578 |
analysis_types.append("Face Detection")
|
1579 |
|
1580 |
+
# New enhanced analysis options
|
1581 |
+
if st.checkbox("Visual Attributes (Colors)", value=False):
|
1582 |
+
analysis_types.append("Visual Attributes")
|
1583 |
+
|
1584 |
st.markdown("---")
|
1585 |
|
1586 |
+
# Confidence threshold control
|
1587 |
+
confidence_threshold = st.slider("Detection Confidence Threshold",
|
1588 |
+
min_value=0.0, max_value=1.0, value=0.5,
|
1589 |
+
help="Filter results based on confidence level")
|
1590 |
+
|
1591 |
# Image quality settings
|
1592 |
st.write("Image settings:")
|
1593 |
quality = st.slider("Image Quality", min_value=0, max_value=100, value=100)
|
1594 |
|
1595 |
+
# Add a slider in the sidebar
|
1596 |
+
confidence_threshold = st.sidebar.slider("Detection Confidence Threshold", 0.0, 1.0, 0.5)
|
1597 |
+
|
1598 |
+
# Then filter results based on this threshold
|
1599 |
+
filtered_objects = [obj for obj in response.localized_object_annotations
|
1600 |
+
if obj.score >= confidence_threshold]
|
1601 |
+
|
1602 |
st.markdown("---")
|
1603 |
st.info("This application analyzes images using Google Cloud Vision AI. Upload an image to get started.")
|
1604 |
|
|
|
1628 |
else:
|
1629 |
with st.spinner("Analyzing image..."):
|
1630 |
# Call analyze function
|
1631 |
+
annotated_img, labels, objects, text, colors, text_language = analyze_image(image, analysis_types)
|
1632 |
|
1633 |
# Display results
|
1634 |
+
display_results(annotated_img, labels, objects, text, colors, text_language)
|
1635 |
|
1636 |
# Add download button for the annotated image
|
1637 |
buf = io.BytesIO()
|
|
|
1651 |
|
1652 |
uploaded_files = st.file_uploader("Choose images...", type=["jpg", "jpeg", "png"], accept_multiple_files=True)
|
1653 |
|
1654 |
+
if uploaded_files and len(uploaded_files) > 0:
|
|
|
1655 |
if len(uploaded_files) > 5:
|
1656 |
+
st.warning("You've uploaded more than 5 images. Only the first 5 will be processed.")
|
1657 |
uploaded_files = uploaded_files[:5]
|
1658 |
|
1659 |
+
if st.button("Process Batch"):
|
1660 |
+
st.write(f"Processing {len(uploaded_files)} images...")
|
1661 |
+
|
1662 |
+
# Process each image with a unique key for each download button
|
1663 |
+
for i, uploaded_file in enumerate(uploaded_files):
|
1664 |
+
st.markdown(f"### Image {i+1}: {uploaded_file.name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1665 |
|
1666 |
+
# Open and process the image
|
1667 |
+
try:
|
1668 |
+
image = Image.open(uploaded_file)
|
1669 |
+
annotated_img, labels, objects, text, colors, text_language = analyze_image(
|
1670 |
+
image, analysis_types, confidence_threshold
|
1671 |
+
)
|
1672 |
+
|
1673 |
+
# Create a unique identifier for this image
|
1674 |
+
image_id = f"{i}_{uploaded_file.name.replace(' ', '_')}"
|
1675 |
+
|
1676 |
+
# Display results with unique download button keys
|
1677 |
+
col1, col2 = st.columns([3, 2])
|
1678 |
+
|
1679 |
+
with col1:
|
1680 |
+
st.image(annotated_img, use_container_width=True)
|
1681 |
+
|
1682 |
+
with col2:
|
1683 |
+
# Display analysis results
|
1684 |
+
if labels:
|
1685 |
+
st.markdown("##### Labels Detected")
|
1686 |
+
for label, confidence in labels.items():
|
1687 |
+
st.write(f"{label}: {confidence}%")
|
1688 |
|
1689 |
+
if objects:
|
1690 |
+
st.markdown("##### Objects Detected")
|
1691 |
+
for obj, confidence in objects.items():
|
1692 |
+
st.write(f"{obj}: {confidence}%")
|
|
|
|
|
1693 |
|
1694 |
+
if text:
|
1695 |
+
st.markdown("##### Text Detected")
|
1696 |
+
if text_language:
|
1697 |
+
st.markdown(f"**Language:** {text_language}")
|
1698 |
+
st.text(text)
|
1699 |
|
1700 |
+
if colors:
|
1701 |
+
st.markdown("##### Dominant Colors")
|
1702 |
+
for color_name, color_data in colors.items():
|
1703 |
+
rgb = color_data["rgb"]
|
1704 |
+
hex_color = f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
|
1705 |
+
st.markdown(f"<div style='background-color:{hex_color};width:50px;height:20px;display:inline-block;'></div> {color_name}: {color_data['score']}%", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1706 |
|
1707 |
+
# Create summary image for download
|
1708 |
+
summary_img = create_summary_image(annotated_img, labels, objects, text, colors)
|
1709 |
+
buf = io.BytesIO()
|
1710 |
+
summary_img.save(buf, format="JPEG", quality=90)
|
1711 |
+
byte_im = buf.getvalue()
|
|
|
|
|
|
|
|
|
|
|
|
|
1712 |
|
1713 |
+
# Use unique key for each download button
|
1714 |
+
st.download_button(
|
1715 |
+
label=f"📥 Download Results for {uploaded_file.name}",
|
1716 |
+
data=byte_im,
|
1717 |
+
file_name=f"analysis_{image_id}.jpg",
|
1718 |
+
mime="image/jpeg",
|
1719 |
+
key=f"download_batch_{image_id}" # Unique key for each image
|
1720 |
+
)
|
|
|
1721 |
|
1722 |
+
st.markdown("---") # Add separator between images
|
1723 |
+
|
1724 |
+
except Exception as e:
|
1725 |
+
st.error(f"Error processing {uploaded_file.name}: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
1726 |
|
1727 |
elif selected == "Video Analysis":
|
1728 |
st.markdown('<div class="subheader">Video Analysis</div>', unsafe_allow_html=True)
|