Spaces:
Runtime error
Runtime error
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)
|