Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1144,7 +1144,72 @@ def process_video_file(video_file, analysis_types, processing_mode="Hybrid (Goog
|
|
1144 |
# Display mode being used
|
1145 |
st.info(f"Processing with {processing_mode} mode")
|
1146 |
|
1147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1148 |
|
1149 |
def load_bigquery_table(dataset_id, table_id, limit=1000):
|
1150 |
"""Load data directly from an existing BigQuery table"""
|
@@ -2273,4 +2338,277 @@ def main():
|
|
2273 |
st.success(f"Successfully uploaded to {dataset_id}.{table_id}")
|
2274 |
st.write(f"Rows: {result['num_rows']}")
|
2275 |
st.write(f"Size: {result['size_bytes'] / 1024:.2f} KB")
|
2276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1144 |
# Display mode being used
|
1145 |
st.info(f"Processing with {processing_mode} mode")
|
1146 |
|
1147 |
+
try:
|
1148 |
+
frame_count = 0
|
1149 |
+
while frame_count < max_frames: # Limit to 10 seconds
|
1150 |
+
ret, frame = cap.read()
|
1151 |
+
if not ret:
|
1152 |
+
break
|
1153 |
+
|
1154 |
+
frame_count += 1
|
1155 |
+
|
1156 |
+
# Update progress
|
1157 |
+
progress = int(frame_count / total_frames * 100)
|
1158 |
+
progress_bar.progress(progress)
|
1159 |
+
status_text.text(f"Processing frame {frame_count}/{total_frames} ({progress}%) - {frame_count/fps:.1f}s of 10s")
|
1160 |
+
|
1161 |
+
# Add timestamp to frame
|
1162 |
+
cv2.putText(frame, f"Time: {frame_count/fps:.2f}s",
|
1163 |
+
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
1164 |
+
|
1165 |
+
# Add processing mode indicator
|
1166 |
+
cv2.putText(frame, f"Mode: {processing_mode}",
|
1167 |
+
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
1168 |
+
|
1169 |
+
# ... rest of the processing code ...
|
1170 |
+
|
1171 |
+
# Write the frame to output video
|
1172 |
+
out.write(frame)
|
1173 |
+
|
1174 |
+
# Release resources
|
1175 |
+
cap.release()
|
1176 |
+
out.release()
|
1177 |
+
|
1178 |
+
# Clear progress indicators
|
1179 |
+
progress_bar.empty()
|
1180 |
+
status_text.empty()
|
1181 |
+
|
1182 |
+
# Read the processed video as bytes for download
|
1183 |
+
with open(output_path, 'rb') as file:
|
1184 |
+
processed_video_bytes = file.read()
|
1185 |
+
|
1186 |
+
# Clean up temporary files
|
1187 |
+
os.unlink(temp_video_path)
|
1188 |
+
os.unlink(output_path)
|
1189 |
+
|
1190 |
+
# Return results
|
1191 |
+
results = {"detection_stats": detection_stats}
|
1192 |
+
|
1193 |
+
# Store results in session state for chatbot context
|
1194 |
+
st.session_state.analysis_results = results
|
1195 |
+
|
1196 |
+
# Update vectorstore with new results
|
1197 |
+
update_vectorstore_with_results(results)
|
1198 |
+
|
1199 |
+
return processed_video_bytes, results
|
1200 |
+
|
1201 |
+
except Exception as e:
|
1202 |
+
# Clean up on error
|
1203 |
+
cap.release()
|
1204 |
+
if 'out' in locals():
|
1205 |
+
out.release()
|
1206 |
+
os.unlink(temp_video_path)
|
1207 |
+
if os.path.exists(output_path):
|
1208 |
+
os.unlink(output_path)
|
1209 |
+
|
1210 |
+
# Return error information
|
1211 |
+
st.error(f"Error processing video: {str(e)}")
|
1212 |
+
return None, None
|
1213 |
|
1214 |
def load_bigquery_table(dataset_id, table_id, limit=1000):
|
1215 |
"""Load data directly from an existing BigQuery table"""
|
|
|
2338 |
st.success(f"Successfully uploaded to {dataset_id}.{table_id}")
|
2339 |
st.write(f"Rows: {result['num_rows']}")
|
2340 |
st.write(f"Size: {result['size_bytes'] / 1024:.2f} KB")
|
2341 |
+
st.write(f"Schema: {', '.join(result['schema'])}")
|
2342 |
+
|
2343 |
+
# Store table info in session state
|
2344 |
+
st.session_state["table_info"] = {
|
2345 |
+
"dataset_id": dataset_id,
|
2346 |
+
"table_id": table_id,
|
2347 |
+
"schema": result["schema"]
|
2348 |
+
}
|
2349 |
+
except Exception as e:
|
2350 |
+
st.error(f"Error uploading to BigQuery: {str(e)}")
|
2351 |
+
except Exception as e:
|
2352 |
+
st.error(f"Error reading CSV file: {str(e)}")
|
2353 |
+
else:
|
2354 |
+
st.info("Upload a CSV file to load data into BigQuery")
|
2355 |
+
|
2356 |
+
with query_tab:
|
2357 |
+
st.markdown("### Query BigQuery Data")
|
2358 |
+
|
2359 |
+
if "query_results" in st.session_state and "table_info" in st.session_state:
|
2360 |
+
# Display info about the loaded data
|
2361 |
+
table_info = st.session_state["table_info"]
|
2362 |
+
st.write(f"Working with table: **{table_info['dataset_id']}.{table_info['table_id']}**")
|
2363 |
+
|
2364 |
+
# Query input
|
2365 |
+
default_query = f"SELECT * FROM `{credentials.project_id}.{table_info['dataset_id']}.{table_info['table_id']}` LIMIT 100"
|
2366 |
+
query = st.text_area("SQL Query", default_query, height=100)
|
2367 |
+
|
2368 |
+
# Execute query button
|
2369 |
+
if st.button("Run Query"):
|
2370 |
+
with st.spinner("Executing query..."):
|
2371 |
+
try:
|
2372 |
+
# Run the query
|
2373 |
+
results = run_bigquery(query)
|
2374 |
+
|
2375 |
+
# Store results in session state
|
2376 |
+
st.session_state["query_results"] = results
|
2377 |
+
|
2378 |
+
# Display results
|
2379 |
+
st.write("### Query Results")
|
2380 |
+
st.dataframe(results)
|
2381 |
+
|
2382 |
+
# Download button for results
|
2383 |
+
csv = results.to_csv(index=False)
|
2384 |
+
st.download_button(
|
2385 |
+
label="Download Results as CSV",
|
2386 |
+
data=csv,
|
2387 |
+
file_name="query_results.csv",
|
2388 |
+
mime="text/csv"
|
2389 |
+
)
|
2390 |
+
except Exception as e:
|
2391 |
+
st.error(f"Error executing query: {str(e)}")
|
2392 |
+
else:
|
2393 |
+
st.info("Load a table from BigQuery or upload a CSV file first")
|
2394 |
+
|
2395 |
+
with visualization_tab:
|
2396 |
+
st.markdown("### Visualize BigQuery Data")
|
2397 |
+
|
2398 |
+
if "query_results" in st.session_state and not st.session_state["query_results"].empty:
|
2399 |
+
df = st.session_state["query_results"]
|
2400 |
+
|
2401 |
+
# Chart type selection
|
2402 |
+
chart_type = st.selectbox(
|
2403 |
+
"Select Chart Type",
|
2404 |
+
["Bar Chart", "Line Chart", "Scatter Plot", "Histogram", "Pie Chart"]
|
2405 |
+
)
|
2406 |
+
|
2407 |
+
# Column selection based on data types
|
2408 |
+
numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns.tolist()
|
2409 |
+
all_cols = df.columns.tolist()
|
2410 |
+
|
2411 |
+
if len(numeric_cols) < 1:
|
2412 |
+
st.warning("No numeric columns available for visualization")
|
2413 |
+
else:
|
2414 |
+
if chart_type in ["Bar Chart", "Line Chart", "Scatter Plot"]:
|
2415 |
+
col1, col2 = st.columns(2)
|
2416 |
+
|
2417 |
+
with col1:
|
2418 |
+
x_axis = st.selectbox("X-axis", all_cols)
|
2419 |
+
|
2420 |
+
with col2:
|
2421 |
+
y_axis = st.selectbox("Y-axis", numeric_cols)
|
2422 |
+
|
2423 |
+
# Optional: Grouping/color dimension
|
2424 |
+
color_dim = st.selectbox("Color Dimension (Optional)", ["None"] + all_cols)
|
2425 |
+
|
2426 |
+
# Generate the visualization based on selection
|
2427 |
+
if st.button("Generate Visualization"):
|
2428 |
+
st.write(f"### {chart_type}: {y_axis} by {x_axis}")
|
2429 |
+
|
2430 |
+
if chart_type == "Bar Chart":
|
2431 |
+
if color_dim != "None":
|
2432 |
+
fig = px.bar(df, x=x_axis, y=y_axis, color=color_dim,
|
2433 |
+
title=f"{y_axis} by {x_axis}")
|
2434 |
+
else:
|
2435 |
+
fig = px.bar(df, x=x_axis, y=y_axis, title=f"{y_axis} by {x_axis}")
|
2436 |
+
st.plotly_chart(fig)
|
2437 |
+
|
2438 |
+
elif chart_type == "Line Chart":
|
2439 |
+
if color_dim != "None":
|
2440 |
+
fig = px.line(df, x=x_axis, y=y_axis, color=color_dim,
|
2441 |
+
title=f"{y_axis} by {x_axis}")
|
2442 |
+
else:
|
2443 |
+
fig = px.line(df, x=x_axis, y=y_axis, title=f"{y_axis} by {x_axis}")
|
2444 |
+
st.plotly_chart(fig)
|
2445 |
+
|
2446 |
+
elif chart_type == "Scatter Plot":
|
2447 |
+
if color_dim != "None":
|
2448 |
+
fig = px.scatter(df, x=x_axis, y=y_axis, color=color_dim,
|
2449 |
+
title=f"{y_axis} vs {x_axis}")
|
2450 |
+
else:
|
2451 |
+
fig = px.scatter(df, x=x_axis, y=y_axis, title=f"{y_axis} vs {x_axis}")
|
2452 |
+
st.plotly_chart(fig)
|
2453 |
+
|
2454 |
+
elif chart_type == "Histogram":
|
2455 |
+
column = st.selectbox("Select Column", numeric_cols)
|
2456 |
+
bins = st.slider("Number of Bins", min_value=5, max_value=100, value=20)
|
2457 |
+
|
2458 |
+
if st.button("Generate Visualization"):
|
2459 |
+
st.write(f"### Histogram of {column}")
|
2460 |
+
fig = px.histogram(df, x=column, nbins=bins, title=f"Distribution of {column}")
|
2461 |
+
st.plotly_chart(fig)
|
2462 |
+
|
2463 |
+
elif chart_type == "Pie Chart":
|
2464 |
+
column = st.selectbox("Category Column", all_cols)
|
2465 |
+
value_col = st.selectbox("Value Column", numeric_cols)
|
2466 |
+
|
2467 |
+
if st.button("Generate Visualization"):
|
2468 |
+
# Aggregate the data if needed
|
2469 |
+
pie_data = df.groupby(column)[value_col].sum().reset_index()
|
2470 |
+
st.write(f"### Pie Chart: {value_col} by {column}")
|
2471 |
+
fig = px.pie(pie_data, names=column, values=value_col,
|
2472 |
+
title=f"{value_col} by {column}")
|
2473 |
+
st.plotly_chart(fig)
|
2474 |
+
else:
|
2475 |
+
st.info("Load a table from BigQuery or upload a CSV file first")
|
2476 |
+
|
2477 |
+
elif selected == "About":
|
2478 |
+
st.markdown("## About This App")
|
2479 |
+
st.write("""
|
2480 |
+
This application uses Google Cloud Vision AI to analyze images and video streams. It can:
|
2481 |
+
|
2482 |
+
- **Detect labels** in images
|
2483 |
+
- **Identify objects** and their locations
|
2484 |
+
- **Extract text** from images
|
2485 |
+
- **Detect faces** and facial landmarks
|
2486 |
+
- **Analyze real-time video** from your camera
|
2487 |
+
|
2488 |
+
To use this app, you need to:
|
2489 |
+
1. Set up Google Cloud Vision API credentials
|
2490 |
+
2. Upload an image or use your camera
|
2491 |
+
3. Select the types of analysis you want to perform
|
2492 |
+
4. Click "Analyze Image" or start the video stream
|
2493 |
+
|
2494 |
+
The app is built with Streamlit and Google Cloud Vision API.
|
2495 |
+
""")
|
2496 |
+
|
2497 |
+
st.info("Note: Make sure your Google Cloud credentials are properly set up to use this application.")
|
2498 |
+
|
2499 |
+
# Add the chatbot interface at the bottom of the page
|
2500 |
+
chatbot_interface()
|
2501 |
+
|
2502 |
+
if __name__ == "__main__":
|
2503 |
+
# Use GOOGLE_CREDENTIALS directly - no need for file or GOOGLE_APPLICATION_CREDENTIALS
|
2504 |
+
try:
|
2505 |
+
if 'GOOGLE_CREDENTIALS' in os.environ:
|
2506 |
+
# Create credentials object directly from JSON string
|
2507 |
+
credentials_info = json.loads(os.environ['GOOGLE_CREDENTIALS'])
|
2508 |
+
credentials = service_account.Credentials.from_service_account_info(credentials_info)
|
2509 |
+
|
2510 |
+
# Initialize client with these credentials directly
|
2511 |
+
client = vision.ImageAnnotatorClient(credentials=credentials)
|
2512 |
+
else:
|
2513 |
+
st.sidebar.error("GOOGLE_CREDENTIALS environment variable not found")
|
2514 |
+
client = None
|
2515 |
+
except Exception as e:
|
2516 |
+
st.sidebar.error(f"Error with credentials: {str(e)}")
|
2517 |
+
client = None
|
2518 |
+
|
2519 |
+
main()
|
2520 |
+
|
2521 |
+
# Add this function to your app
|
2522 |
+
def extract_video_frames(video_bytes, num_frames=5):
|
2523 |
+
"""Extract frames from video bytes for thumbnail display with improved key frame selection"""
|
2524 |
+
import cv2
|
2525 |
+
import numpy as np
|
2526 |
+
import tempfile
|
2527 |
+
from PIL import Image
|
2528 |
+
import io
|
2529 |
+
|
2530 |
+
# Save video bytes to a temporary file
|
2531 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
|
2532 |
+
temp_file.write(video_bytes)
|
2533 |
+
temp_video_path = temp_file.name
|
2534 |
+
|
2535 |
+
# Open the video file
|
2536 |
+
cap = cv2.VideoCapture(temp_video_path)
|
2537 |
+
|
2538 |
+
# Get video properties
|
2539 |
+
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
2540 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
2541 |
+
|
2542 |
+
# Use more sophisticated frame selection based on content analysis
|
2543 |
+
frames = []
|
2544 |
+
frame_scores = []
|
2545 |
+
sample_interval = max(1, frame_count // (num_frames * 3)) # Sample more frames than needed
|
2546 |
+
|
2547 |
+
# First pass: collect frame scores
|
2548 |
+
prev_frame = None
|
2549 |
+
frame_index = 0
|
2550 |
+
|
2551 |
+
while len(frame_scores) < num_frames * 3 and frame_index < frame_count:
|
2552 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
|
2553 |
+
ret, frame = cap.read()
|
2554 |
+
if not ret:
|
2555 |
+
break
|
2556 |
+
|
2557 |
+
# Convert to grayscale for analysis
|
2558 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
2559 |
+
gray = cv2.GaussianBlur(gray, (21, 21), 0)
|
2560 |
+
|
2561 |
+
# Calculate frame score based on Laplacian variance (focus measure)
|
2562 |
+
focus_score = cv2.Laplacian(gray, cv2.CV_64F).var()
|
2563 |
+
|
2564 |
+
# Calculate frame difference if we have a previous frame
|
2565 |
+
diff_score = 0
|
2566 |
+
if prev_frame is not None:
|
2567 |
+
frame_diff = cv2.absdiff(gray, prev_frame)
|
2568 |
+
diff_score = np.mean(frame_diff)
|
2569 |
+
|
2570 |
+
# Combined score: favor sharp frames with significant changes
|
2571 |
+
combined_score = focus_score * 0.6 + diff_score * 0.4
|
2572 |
+
frame_scores.append((frame_index, combined_score))
|
2573 |
+
|
2574 |
+
# Store frame for next comparison
|
2575 |
+
prev_frame = gray
|
2576 |
+
frame_index += sample_interval
|
2577 |
+
|
2578 |
+
# Second pass: select the best frames based on scores
|
2579 |
+
# Sort by score and get top N frames
|
2580 |
+
sorted_frames = sorted(frame_scores, key=lambda x: x[1], reverse=True)
|
2581 |
+
best_frames = sorted_frames[:num_frames]
|
2582 |
+
# Sort back by frame index to maintain chronological order
|
2583 |
+
selected_frames = sorted(best_frames, key=lambda x: x[0])
|
2584 |
+
|
2585 |
+
# Extract the selected frames
|
2586 |
+
for idx, _ in selected_frames:
|
2587 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
|
2588 |
+
ret, frame = cap.read()
|
2589 |
+
if ret:
|
2590 |
+
# Apply subtle enhancement to frames
|
2591 |
+
enhanced_frame = frame.copy()
|
2592 |
+
# Auto color balance
|
2593 |
+
lab = cv2.cvtColor(enhanced_frame, cv2.COLOR_BGR2LAB)
|
2594 |
+
l, a, b = cv2.split(lab)
|
2595 |
+
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
|
2596 |
+
cl = clahe.apply(l)
|
2597 |
+
enhanced_lab = cv2.merge((cl, a, b))
|
2598 |
+
enhanced_frame = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2BGR)
|
2599 |
+
|
2600 |
+
# Convert to RGB (from BGR)
|
2601 |
+
frame_rgb = cv2.cvtColor(enhanced_frame, cv2.COLOR_BGR2RGB)
|
2602 |
+
# Convert to PIL Image
|
2603 |
+
pil_img = Image.fromarray(frame_rgb)
|
2604 |
+
# Save to bytes
|
2605 |
+
img_byte_arr = io.BytesIO()
|
2606 |
+
pil_img.save(img_byte_arr, format='JPEG', quality=90)
|
2607 |
+
frames.append(img_byte_arr.getvalue())
|
2608 |
+
|
2609 |
+
# Clean up
|
2610 |
+
cap.release()
|
2611 |
+
import os
|
2612 |
+
os.unlink(temp_video_path)
|
2613 |
+
|
2614 |
+
return frames
|