Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -20,6 +20,7 @@ import tempfile
|
|
20 |
import time
|
21 |
import matplotlib.pyplot as plt
|
22 |
from pathlib import Path
|
|
|
23 |
|
24 |
# Set page config
|
25 |
st.set_page_config(
|
@@ -462,7 +463,7 @@ def list_bigquery_resources():
|
|
462 |
return resources
|
463 |
|
464 |
def process_video_file(video_file, analysis_types):
|
465 |
-
"""Process an uploaded video file with enhanced Vision AI detection"""
|
466 |
# Create a temporary file to save the uploaded video
|
467 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
|
468 |
temp_file.write(video_file.read())
|
@@ -491,9 +492,13 @@ def process_video_file(video_file, analysis_types):
|
|
491 |
if int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) > max_frames:
|
492 |
st.info("⚠️ Video is longer than 10 seconds. Only the first 10 seconds will be processed.")
|
493 |
|
494 |
-
#
|
495 |
-
|
496 |
-
|
|
|
|
|
|
|
|
|
497 |
|
498 |
# Process every Nth frame to reduce API calls but increase from 10 to 5 for more detail
|
499 |
process_every_n_frames = 5
|
@@ -556,28 +561,28 @@ def process_video_file(video_file, analysis_types):
|
|
556 |
for vertex in obj.bounding_poly.normalized_vertices]
|
557 |
box = np.array(box, np.int32).reshape((-1, 1, 2))
|
558 |
|
559 |
-
# Draw more
|
560 |
-
cv2.polylines(frame, [box], True, (0, 255, 0),
|
561 |
|
562 |
# Calculate box size for better placement of labels
|
563 |
x_min = min([p[0][0] for p in box])
|
564 |
y_min = min([p[0][1] for p in box])
|
565 |
confidence = int(obj.score * 100)
|
566 |
|
567 |
-
# Enhanced label with confidence and border
|
568 |
label_text = f"{obj.name}: {confidence}%"
|
569 |
-
text_size = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.
|
570 |
|
571 |
-
#
|
572 |
cv2.rectangle(frame,
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
|
577 |
-
# Draw the label text
|
578 |
cv2.putText(frame, label_text,
|
579 |
(int(x_min) + 5, int(y_min) - 5),
|
580 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.
|
581 |
|
582 |
if "Face Detection" in analysis_types:
|
583 |
faces = client.face_detection(image=vision_image)
|
@@ -587,9 +592,9 @@ def process_video_file(video_file, analysis_types):
|
|
587 |
for face in faces.face_annotations:
|
588 |
vertices = face.bounding_poly.vertices
|
589 |
points = [(vertex.x, vertex.y) for vertex in vertices]
|
590 |
-
# Draw face box
|
591 |
pts = np.array(points, np.int32).reshape((-1, 1, 2))
|
592 |
-
cv2.polylines(frame, [pts], True, (0, 0, 255),
|
593 |
|
594 |
# Enhanced face info
|
595 |
emotions = []
|
@@ -609,13 +614,13 @@ def process_video_file(video_file, analysis_types):
|
|
609 |
# Add detailed emotion text
|
610 |
cv2.putText(frame, emotion_text,
|
611 |
(int(x_min), int(y_min) - 10),
|
612 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.
|
613 |
|
614 |
# Draw enhanced landmarks
|
615 |
for landmark in face.landmarks:
|
616 |
px = int(landmark.position.x)
|
617 |
py = int(landmark.position.y)
|
618 |
-
cv2.circle(frame, (px, py),
|
619 |
|
620 |
if "Text" in analysis_types:
|
621 |
text = client.text_detection(image=vision_image)
|
@@ -631,25 +636,25 @@ def process_video_file(video_file, analysis_types):
|
|
631 |
if len(words) > 5:
|
632 |
short_text += "..."
|
633 |
|
634 |
-
# Add text summary to top of frame
|
635 |
cv2.rectangle(frame, (10, 60), (10 + len(short_text)*10, 90), (0, 0, 0), -1)
|
636 |
cv2.putText(frame, f"Text: {short_text}",
|
637 |
-
(10, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.
|
638 |
|
639 |
# Draw text boxes with improved visibility
|
640 |
for text_annot in text.text_annotations[1:]:
|
641 |
box = [(vertex.x, vertex.y) for vertex in text_annot.bounding_poly.vertices]
|
642 |
pts = np.array(box, np.int32).reshape((-1, 1, 2))
|
643 |
-
cv2.polylines(frame, [pts], True, (255, 0, 0),
|
644 |
|
645 |
# Add Labels analysis for more detail
|
646 |
if "Labels" in analysis_types:
|
647 |
labels = client.label_detection(image=vision_image, max_results=5)
|
648 |
|
649 |
-
# Add labels to the frame
|
650 |
y_pos = 120
|
651 |
-
cv2.rectangle(frame, (10, y_pos-20), (
|
652 |
-
cv2.putText(frame, "Scene labels:", (15, y_pos), cv2.FONT_HERSHEY_SIMPLEX, 0.
|
653 |
|
654 |
# Track stats and show labels
|
655 |
for i, label in enumerate(labels.label_annotations):
|
@@ -659,15 +664,19 @@ def process_video_file(video_file, analysis_types):
|
|
659 |
else:
|
660 |
detection_stats["labels"][label.description] = 1
|
661 |
|
662 |
-
# Display on frame
|
663 |
cv2.putText(frame, f"- {label.description}: {int(label.score*100)}%",
|
664 |
-
(15, y_pos + 20*(i+1)), cv2.FONT_HERSHEY_SIMPLEX, 0.
|
665 |
|
666 |
except Exception as e:
|
667 |
# Show error on frame
|
668 |
cv2.putText(frame, f"API Error: {str(e)[:30]}",
|
669 |
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
|
670 |
|
|
|
|
|
|
|
|
|
671 |
# Write the frame to output video
|
672 |
out.write(frame)
|
673 |
|
@@ -730,8 +739,8 @@ def load_bigquery_table(dataset_id, table_id, limit=1000):
|
|
730 |
}
|
731 |
|
732 |
def main():
|
733 |
-
# Header
|
734 |
-
st.markdown('<div class="main-header">
|
735 |
|
736 |
# Navigation
|
737 |
selected = option_menu(
|
@@ -748,6 +757,9 @@ def main():
|
|
748 |
with st.sidebar:
|
749 |
st.markdown("### Analysis Settings")
|
750 |
|
|
|
|
|
|
|
751 |
# Analysis types selection
|
752 |
st.write("Choose analysis types:")
|
753 |
analysis_types = []
|
@@ -774,45 +786,166 @@ def main():
|
|
774 |
st.info("This application analyzes images using Google Cloud Vision AI. Upload an image to get started.")
|
775 |
|
776 |
# Main content
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
# Convert uploaded file to image
|
781 |
-
image = Image.open(uploaded_file)
|
782 |
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
788 |
|
789 |
-
|
790 |
-
st.markdown('<div class="subheader">Original Image</div>', unsafe_allow_html=True)
|
791 |
-
st.image(image, use_column_width=True)
|
792 |
|
793 |
-
|
794 |
-
|
795 |
-
if
|
796 |
-
st.warning("
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
801 |
|
802 |
-
#
|
803 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
804 |
|
805 |
-
|
806 |
-
buf = io.BytesIO()
|
807 |
-
annotated_img.save(buf, format="PNG")
|
808 |
-
byte_im = buf.getvalue()
|
809 |
|
810 |
-
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
816 |
|
817 |
elif selected == "Video Analysis":
|
818 |
st.markdown('<div class="subheader">Video Analysis</div>', unsafe_allow_html=True)
|
@@ -1138,6 +1271,176 @@ def main():
|
|
1138 |
st.dataframe(df[num_cols].describe())
|
1139 |
else:
|
1140 |
st.info("Select an existing dataset and table from the sidebar and click 'Load Selected Table', or upload a CSV file in the 'Upload Data' tab.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1141 |
|
1142 |
elif selected == "About":
|
1143 |
st.markdown("## About This App")
|
@@ -1161,6 +1464,17 @@ def main():
|
|
1161 |
|
1162 |
st.info("Note: Make sure your Google Cloud credentials are properly set up to use this application.")
|
1163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1164 |
if __name__ == "__main__":
|
1165 |
# Use GOOGLE_CREDENTIALS directly - no need for file or GOOGLE_APPLICATION_CREDENTIALS
|
1166 |
try:
|
|
|
20 |
import time
|
21 |
import matplotlib.pyplot as plt
|
22 |
from pathlib import Path
|
23 |
+
import plotly.express as px
|
24 |
|
25 |
# Set page config
|
26 |
st.set_page_config(
|
|
|
463 |
return resources
|
464 |
|
465 |
def process_video_file(video_file, analysis_types):
|
466 |
+
"""Process an uploaded video file with enhanced Vision AI detection and slow down output for better visibility"""
|
467 |
# Create a temporary file to save the uploaded video
|
468 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
|
469 |
temp_file.write(video_file.read())
|
|
|
492 |
if int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) > max_frames:
|
493 |
st.info("⚠️ Video is longer than 10 seconds. Only the first 10 seconds will be processed.")
|
494 |
|
495 |
+
# Slow down the output video by reducing the fps (60% of original speed)
|
496 |
+
output_fps = fps * 0.6
|
497 |
+
st.info(f"Output video will be slowed down to {output_fps:.1f} FPS (60% of original speed) for better visualization.")
|
498 |
+
|
499 |
+
# Create video writer with higher quality settings
|
500 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # MP4 codec
|
501 |
+
out = cv2.VideoWriter(output_path, fourcc, output_fps, (width, height), isColor=True)
|
502 |
|
503 |
# Process every Nth frame to reduce API calls but increase from 10 to 5 for more detail
|
504 |
process_every_n_frames = 5
|
|
|
561 |
for vertex in obj.bounding_poly.normalized_vertices]
|
562 |
box = np.array(box, np.int32).reshape((-1, 1, 2))
|
563 |
|
564 |
+
# Draw more noticeable box with thicker lines
|
565 |
+
cv2.polylines(frame, [box], True, (0, 255, 0), 3)
|
566 |
|
567 |
# Calculate box size for better placement of labels
|
568 |
x_min = min([p[0][0] for p in box])
|
569 |
y_min = min([p[0][1] for p in box])
|
570 |
confidence = int(obj.score * 100)
|
571 |
|
572 |
+
# Enhanced label with confidence and border - larger text for visibility
|
573 |
label_text = f"{obj.name}: {confidence}%"
|
574 |
+
text_size = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)[0]
|
575 |
|
576 |
+
# Larger background rectangle for text visibility
|
577 |
cv2.rectangle(frame,
|
578 |
+
(int(x_min), int(y_min) - text_size[1] - 10),
|
579 |
+
(int(x_min) + text_size[0] + 10, int(y_min)),
|
580 |
+
(0, 0, 0), -1)
|
581 |
|
582 |
+
# Draw the label text with larger font
|
583 |
cv2.putText(frame, label_text,
|
584 |
(int(x_min) + 5, int(y_min) - 5),
|
585 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
586 |
|
587 |
if "Face Detection" in analysis_types:
|
588 |
faces = client.face_detection(image=vision_image)
|
|
|
592 |
for face in faces.face_annotations:
|
593 |
vertices = face.bounding_poly.vertices
|
594 |
points = [(vertex.x, vertex.y) for vertex in vertices]
|
595 |
+
# Draw face box with thicker lines
|
596 |
pts = np.array(points, np.int32).reshape((-1, 1, 2))
|
597 |
+
cv2.polylines(frame, [pts], True, (0, 0, 255), 3)
|
598 |
|
599 |
# Enhanced face info
|
600 |
emotions = []
|
|
|
614 |
# Add detailed emotion text
|
615 |
cv2.putText(frame, emotion_text,
|
616 |
(int(x_min), int(y_min) - 10),
|
617 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
|
618 |
|
619 |
# Draw enhanced landmarks
|
620 |
for landmark in face.landmarks:
|
621 |
px = int(landmark.position.x)
|
622 |
py = int(landmark.position.y)
|
623 |
+
cv2.circle(frame, (px, py), 3, (255, 255, 0), -1) # Larger circles
|
624 |
|
625 |
if "Text" in analysis_types:
|
626 |
text = client.text_detection(image=vision_image)
|
|
|
636 |
if len(words) > 5:
|
637 |
short_text += "..."
|
638 |
|
639 |
+
# Add text summary to top of frame with better visibility
|
640 |
cv2.rectangle(frame, (10, 60), (10 + len(short_text)*10, 90), (0, 0, 0), -1)
|
641 |
cv2.putText(frame, f"Text: {short_text}",
|
642 |
+
(10, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
643 |
|
644 |
# Draw text boxes with improved visibility
|
645 |
for text_annot in text.text_annotations[1:]:
|
646 |
box = [(vertex.x, vertex.y) for vertex in text_annot.bounding_poly.vertices]
|
647 |
pts = np.array(box, np.int32).reshape((-1, 1, 2))
|
648 |
+
cv2.polylines(frame, [pts], True, (255, 0, 0), 2) # Thicker lines
|
649 |
|
650 |
# Add Labels analysis for more detail
|
651 |
if "Labels" in analysis_types:
|
652 |
labels = client.label_detection(image=vision_image, max_results=5)
|
653 |
|
654 |
+
# Add labels to the frame with better visibility
|
655 |
y_pos = 120
|
656 |
+
cv2.rectangle(frame, (10, y_pos-20), (250, y_pos+20*len(labels.label_annotations)), (0, 0, 0), -1)
|
657 |
+
cv2.putText(frame, "Scene labels:", (15, y_pos), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
658 |
|
659 |
# Track stats and show labels
|
660 |
for i, label in enumerate(labels.label_annotations):
|
|
|
664 |
else:
|
665 |
detection_stats["labels"][label.description] = 1
|
666 |
|
667 |
+
# Display on frame with larger text
|
668 |
cv2.putText(frame, f"- {label.description}: {int(label.score*100)}%",
|
669 |
+
(15, y_pos + 20*(i+1)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
670 |
|
671 |
except Exception as e:
|
672 |
# Show error on frame
|
673 |
cv2.putText(frame, f"API Error: {str(e)[:30]}",
|
674 |
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
|
675 |
|
676 |
+
# Add hint about slowed down speed
|
677 |
+
cv2.putText(frame, "Playback: 60% speed for better visualization",
|
678 |
+
(width - 400, height - 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 200, 0), 2)
|
679 |
+
|
680 |
# Write the frame to output video
|
681 |
out.write(frame)
|
682 |
|
|
|
739 |
}
|
740 |
|
741 |
def main():
|
742 |
+
# Header - Updated title
|
743 |
+
st.markdown('<div class="main-header">Cosmick Cloud AI Analyzer</div>', unsafe_allow_html=True)
|
744 |
|
745 |
# Navigation
|
746 |
selected = option_menu(
|
|
|
757 |
with st.sidebar:
|
758 |
st.markdown("### Analysis Settings")
|
759 |
|
760 |
+
# Add mode selection
|
761 |
+
processing_mode = st.radio("Processing Mode", ["Single Image", "Batch Processing (up to 5 images)"])
|
762 |
+
|
763 |
# Analysis types selection
|
764 |
st.write("Choose analysis types:")
|
765 |
analysis_types = []
|
|
|
786 |
st.info("This application analyzes images using Google Cloud Vision AI. Upload an image to get started.")
|
787 |
|
788 |
# Main content
|
789 |
+
if processing_mode == "Single Image":
|
790 |
+
st.markdown("## Single Image Analysis")
|
791 |
+
uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
|
|
|
|
|
792 |
|
793 |
+
if uploaded_file is not None:
|
794 |
+
# Convert uploaded file to image
|
795 |
+
image = Image.open(uploaded_file)
|
796 |
+
|
797 |
+
# Apply quality adjustment if needed
|
798 |
+
if quality < 100:
|
799 |
+
img_byte_arr = io.BytesIO()
|
800 |
+
image.save(img_byte_arr, format='JPEG', quality=quality)
|
801 |
+
image = Image.open(img_byte_arr)
|
802 |
+
|
803 |
+
# Show original image
|
804 |
+
st.markdown('<div class="subheader">Original Image</div>', unsafe_allow_html=True)
|
805 |
+
st.image(image, use_column_width=True)
|
806 |
+
|
807 |
+
# Add analyze button
|
808 |
+
if st.button("Analyze Image"):
|
809 |
+
if not analysis_types:
|
810 |
+
st.warning("Please select at least one analysis type.")
|
811 |
+
else:
|
812 |
+
with st.spinner("Analyzing image..."):
|
813 |
+
# Call analyze function
|
814 |
+
annotated_img, labels, objects, text = analyze_image(image, analysis_types)
|
815 |
+
|
816 |
+
# Display results
|
817 |
+
display_results(annotated_img, labels, objects, text)
|
818 |
+
|
819 |
+
# Add download button for the annotated image
|
820 |
+
buf = io.BytesIO()
|
821 |
+
annotated_img.save(buf, format="PNG")
|
822 |
+
byte_im = buf.getvalue()
|
823 |
+
|
824 |
+
st.download_button(
|
825 |
+
label="Download Annotated Image",
|
826 |
+
data=byte_im,
|
827 |
+
file_name="annotated_image.png",
|
828 |
+
mime="image/png"
|
829 |
+
)
|
830 |
+
|
831 |
+
else: # Batch Processing mode
|
832 |
+
st.markdown("## Batch Image Analysis")
|
833 |
+
st.info("Upload up to 5 images for batch processing.")
|
834 |
|
835 |
+
uploaded_files = st.file_uploader("Choose images...", type=["jpg", "jpeg", "png"], accept_multiple_files=True)
|
|
|
|
|
836 |
|
837 |
+
if uploaded_files:
|
838 |
+
# Limit to 5 images
|
839 |
+
if len(uploaded_files) > 5:
|
840 |
+
st.warning("Maximum 5 images allowed. Only the first 5 will be processed.")
|
841 |
+
uploaded_files = uploaded_files[:5]
|
842 |
+
|
843 |
+
# Display thumbnails of uploaded images
|
844 |
+
st.markdown('<div class="subheader">Uploaded Images</div>', unsafe_allow_html=True)
|
845 |
+
cols = st.columns(len(uploaded_files))
|
846 |
+
for i, uploaded_file in enumerate(uploaded_files):
|
847 |
+
with cols[i]:
|
848 |
+
image = Image.open(uploaded_file)
|
849 |
+
st.image(image, caption=f"Image {i+1}", use_column_width=True)
|
850 |
+
|
851 |
+
# Add analyze button for batch processing
|
852 |
+
if st.button("Analyze All Images"):
|
853 |
+
if not analysis_types:
|
854 |
+
st.warning("Please select at least one analysis type.")
|
855 |
+
else:
|
856 |
+
# Initialize containers for batch summary
|
857 |
+
all_labels = {}
|
858 |
+
all_objects = {}
|
859 |
|
860 |
+
# Process each image
|
861 |
+
for i, uploaded_file in enumerate(uploaded_files):
|
862 |
+
with st.spinner(f"Analyzing image {i+1} of {len(uploaded_files)}..."):
|
863 |
+
# Convert uploaded file to image
|
864 |
+
image = Image.open(uploaded_file)
|
865 |
+
|
866 |
+
# Apply quality adjustment if needed
|
867 |
+
if quality < 100:
|
868 |
+
img_byte_arr = io.BytesIO()
|
869 |
+
image.save(img_byte_arr, format='JPEG', quality=quality)
|
870 |
+
image = Image.open(img_byte_arr)
|
871 |
+
|
872 |
+
# Analyze image
|
873 |
+
annotated_img, labels, objects, text = analyze_image(image, analysis_types)
|
874 |
+
|
875 |
+
# Update batch summaries
|
876 |
+
for label, confidence in labels.items():
|
877 |
+
if label in all_labels:
|
878 |
+
all_labels[label] = max(all_labels[label], confidence)
|
879 |
+
else:
|
880 |
+
all_labels[label] = confidence
|
881 |
+
|
882 |
+
for obj, confidence in objects.items():
|
883 |
+
if obj in all_objects:
|
884 |
+
all_objects[obj] = max(all_objects[obj], confidence)
|
885 |
+
else:
|
886 |
+
all_objects[obj] = confidence
|
887 |
+
|
888 |
+
# Create expander for each image result
|
889 |
+
with st.expander(f"Results for Image {i+1}", expanded=i==0):
|
890 |
+
# Display results for this image
|
891 |
+
display_results(annotated_img, labels, objects, text)
|
892 |
+
|
893 |
+
# Add download button for each annotated image
|
894 |
+
buf = io.BytesIO()
|
895 |
+
annotated_img.save(buf, format="PNG")
|
896 |
+
byte_im = buf.getvalue()
|
897 |
+
|
898 |
+
st.download_button(
|
899 |
+
label=f"Download Annotated Image {i+1}",
|
900 |
+
data=byte_im,
|
901 |
+
file_name=f"annotated_image_{i+1}.png",
|
902 |
+
mime="image/png"
|
903 |
+
)
|
904 |
+
|
905 |
+
# Display batch summary
|
906 |
+
st.markdown('<div class="subheader">Batch Analysis Summary</div>', unsafe_allow_html=True)
|
907 |
|
908 |
+
col1, col2 = st.columns(2)
|
|
|
|
|
|
|
909 |
|
910 |
+
with col1:
|
911 |
+
if all_labels:
|
912 |
+
st.markdown("#### Common Labels Across Images")
|
913 |
+
# Sort by confidence
|
914 |
+
sorted_labels = dict(sorted(all_labels.items(), key=lambda x: x[1], reverse=True))
|
915 |
+
for label, confidence in sorted_labels.items():
|
916 |
+
st.markdown(f'<div class="label-item">{label}: {confidence}%</div>', unsafe_allow_html=True)
|
917 |
+
|
918 |
+
with col2:
|
919 |
+
if all_objects:
|
920 |
+
st.markdown("#### Common Objects Across Images")
|
921 |
+
# Sort by confidence
|
922 |
+
sorted_objects = dict(sorted(all_objects.items(), key=lambda x: x[1], reverse=True))
|
923 |
+
for obj, confidence in sorted_objects.items():
|
924 |
+
st.markdown(f'<div class="object-item">{obj}: {confidence}%</div>', unsafe_allow_html=True)
|
925 |
+
|
926 |
+
# Create visualization for batch summary if there are labels or objects
|
927 |
+
if all_labels or all_objects:
|
928 |
+
st.markdown("#### Visual Summary")
|
929 |
+
|
930 |
+
# Create label chart
|
931 |
+
if all_labels:
|
932 |
+
fig_labels = px.bar(
|
933 |
+
x=list(all_labels.keys()),
|
934 |
+
y=list(all_labels.values()),
|
935 |
+
labels={'x': 'Label', 'y': 'Confidence (%)'},
|
936 |
+
title='Top Labels Across All Images'
|
937 |
+
)
|
938 |
+
st.plotly_chart(fig_labels)
|
939 |
+
|
940 |
+
# Create object chart
|
941 |
+
if all_objects:
|
942 |
+
fig_objects = px.bar(
|
943 |
+
x=list(all_objects.keys()),
|
944 |
+
y=list(all_objects.values()),
|
945 |
+
labels={'x': 'Object', 'y': 'Confidence (%)'},
|
946 |
+
title='Top Objects Across All Images'
|
947 |
+
)
|
948 |
+
st.plotly_chart(fig_objects)
|
949 |
|
950 |
elif selected == "Video Analysis":
|
951 |
st.markdown('<div class="subheader">Video Analysis</div>', unsafe_allow_html=True)
|
|
|
1271 |
st.dataframe(df[num_cols].describe())
|
1272 |
else:
|
1273 |
st.info("Select an existing dataset and table from the sidebar and click 'Load Selected Table', or upload a CSV file in the 'Upload Data' tab.")
|
1274 |
+
|
1275 |
+
with upload_tab:
|
1276 |
+
st.markdown("### Upload Data to BigQuery")
|
1277 |
+
|
1278 |
+
# File uploader for CSV files
|
1279 |
+
uploaded_file = st.file_uploader("Upload a CSV file", type=["csv"])
|
1280 |
+
|
1281 |
+
if uploaded_file is not None:
|
1282 |
+
# Display file details
|
1283 |
+
file_details = {
|
1284 |
+
"Filename": uploaded_file.name,
|
1285 |
+
"File size": f"{uploaded_file.size / 1024:.2f} KB"
|
1286 |
+
}
|
1287 |
+
|
1288 |
+
# Show file preview
|
1289 |
+
try:
|
1290 |
+
df_preview = pd.read_csv(uploaded_file)
|
1291 |
+
st.write("### File Preview")
|
1292 |
+
st.dataframe(df_preview.head(5))
|
1293 |
+
|
1294 |
+
# Store dataframe in session state for other tabs
|
1295 |
+
st.session_state["query_results"] = df_preview
|
1296 |
+
|
1297 |
+
# Upload button
|
1298 |
+
if st.button("Upload to BigQuery"):
|
1299 |
+
with st.spinner("Uploading to BigQuery..."):
|
1300 |
+
try:
|
1301 |
+
# Upload the file
|
1302 |
+
append = replace_data == "Append to existing data"
|
1303 |
+
result = upload_csv_to_bigquery(uploaded_file, dataset_id, table_id, append=append)
|
1304 |
+
|
1305 |
+
# Show success message
|
1306 |
+
st.success(f"Successfully uploaded to {dataset_id}.{table_id}")
|
1307 |
+
st.write(f"Rows: {result['num_rows']}")
|
1308 |
+
st.write(f"Size: {result['size_bytes'] / 1024:.2f} KB")
|
1309 |
+
st.write(f"Schema: {', '.join(result['schema'])}")
|
1310 |
+
|
1311 |
+
# Store table info in session state
|
1312 |
+
st.session_state["table_info"] = {
|
1313 |
+
"dataset_id": dataset_id,
|
1314 |
+
"table_id": table_id,
|
1315 |
+
"schema": result["schema"]
|
1316 |
+
}
|
1317 |
+
except Exception as e:
|
1318 |
+
st.error(f"Error uploading to BigQuery: {str(e)}")
|
1319 |
+
except Exception as e:
|
1320 |
+
st.error(f"Error reading CSV file: {str(e)}")
|
1321 |
+
else:
|
1322 |
+
st.info("Upload a CSV file to load data into BigQuery")
|
1323 |
+
|
1324 |
+
with query_tab:
|
1325 |
+
st.markdown("### Query BigQuery Data")
|
1326 |
+
|
1327 |
+
if "query_results" in st.session_state and "table_info" in st.session_state:
|
1328 |
+
# Display info about the loaded data
|
1329 |
+
table_info = st.session_state["table_info"]
|
1330 |
+
st.write(f"Working with table: **{table_info['dataset_id']}.{table_info['table_id']}**")
|
1331 |
+
|
1332 |
+
# Query input
|
1333 |
+
default_query = f"SELECT * FROM `{credentials.project_id}.{table_info['dataset_id']}.{table_info['table_id']}` LIMIT 100"
|
1334 |
+
query = st.text_area("SQL Query", default_query, height=100)
|
1335 |
+
|
1336 |
+
# Execute query button
|
1337 |
+
if st.button("Run Query"):
|
1338 |
+
with st.spinner("Executing query..."):
|
1339 |
+
try:
|
1340 |
+
# Run the query
|
1341 |
+
results = run_bigquery(query)
|
1342 |
+
|
1343 |
+
# Store results in session state
|
1344 |
+
st.session_state["query_results"] = results
|
1345 |
+
|
1346 |
+
# Display results
|
1347 |
+
st.write("### Query Results")
|
1348 |
+
st.dataframe(results)
|
1349 |
+
|
1350 |
+
# Download button for results
|
1351 |
+
csv = results.to_csv(index=False)
|
1352 |
+
st.download_button(
|
1353 |
+
label="Download Results as CSV",
|
1354 |
+
data=csv,
|
1355 |
+
file_name="query_results.csv",
|
1356 |
+
mime="text/csv"
|
1357 |
+
)
|
1358 |
+
except Exception as e:
|
1359 |
+
st.error(f"Error executing query: {str(e)}")
|
1360 |
+
else:
|
1361 |
+
st.info("Load a table from BigQuery or upload a CSV file first")
|
1362 |
+
|
1363 |
+
with visualization_tab:
|
1364 |
+
st.markdown("### Visualize BigQuery Data")
|
1365 |
+
|
1366 |
+
if "query_results" in st.session_state and not st.session_state["query_results"].empty:
|
1367 |
+
df = st.session_state["query_results"]
|
1368 |
+
|
1369 |
+
# Chart type selection
|
1370 |
+
chart_type = st.selectbox(
|
1371 |
+
"Select Chart Type",
|
1372 |
+
["Bar Chart", "Line Chart", "Scatter Plot", "Histogram", "Pie Chart"]
|
1373 |
+
)
|
1374 |
+
|
1375 |
+
# Column selection based on data types
|
1376 |
+
numeric_cols = df.select_dtypes(include=['int64', 'float64']).columns.tolist()
|
1377 |
+
all_cols = df.columns.tolist()
|
1378 |
+
|
1379 |
+
if len(numeric_cols) < 1:
|
1380 |
+
st.warning("No numeric columns available for visualization")
|
1381 |
+
else:
|
1382 |
+
if chart_type in ["Bar Chart", "Line Chart", "Scatter Plot"]:
|
1383 |
+
col1, col2 = st.columns(2)
|
1384 |
+
|
1385 |
+
with col1:
|
1386 |
+
x_axis = st.selectbox("X-axis", all_cols)
|
1387 |
+
|
1388 |
+
with col2:
|
1389 |
+
y_axis = st.selectbox("Y-axis", numeric_cols)
|
1390 |
+
|
1391 |
+
# Optional: Grouping/color dimension
|
1392 |
+
color_dim = st.selectbox("Color Dimension (Optional)", ["None"] + all_cols)
|
1393 |
+
|
1394 |
+
# Generate the visualization based on selection
|
1395 |
+
if st.button("Generate Visualization"):
|
1396 |
+
st.write(f"### {chart_type}: {y_axis} by {x_axis}")
|
1397 |
+
|
1398 |
+
if chart_type == "Bar Chart":
|
1399 |
+
if color_dim != "None":
|
1400 |
+
fig = px.bar(df, x=x_axis, y=y_axis, color=color_dim,
|
1401 |
+
title=f"{y_axis} by {x_axis}")
|
1402 |
+
else:
|
1403 |
+
fig = px.bar(df, x=x_axis, y=y_axis, title=f"{y_axis} by {x_axis}")
|
1404 |
+
st.plotly_chart(fig)
|
1405 |
+
|
1406 |
+
elif chart_type == "Line Chart":
|
1407 |
+
if color_dim != "None":
|
1408 |
+
fig = px.line(df, x=x_axis, y=y_axis, color=color_dim,
|
1409 |
+
title=f"{y_axis} by {x_axis}")
|
1410 |
+
else:
|
1411 |
+
fig = px.line(df, x=x_axis, y=y_axis, title=f"{y_axis} by {x_axis}")
|
1412 |
+
st.plotly_chart(fig)
|
1413 |
+
|
1414 |
+
elif chart_type == "Scatter Plot":
|
1415 |
+
if color_dim != "None":
|
1416 |
+
fig = px.scatter(df, x=x_axis, y=y_axis, color=color_dim,
|
1417 |
+
title=f"{y_axis} vs {x_axis}")
|
1418 |
+
else:
|
1419 |
+
fig = px.scatter(df, x=x_axis, y=y_axis, title=f"{y_axis} vs {x_axis}")
|
1420 |
+
st.plotly_chart(fig)
|
1421 |
+
|
1422 |
+
elif chart_type == "Histogram":
|
1423 |
+
column = st.selectbox("Select Column", numeric_cols)
|
1424 |
+
bins = st.slider("Number of Bins", min_value=5, max_value=100, value=20)
|
1425 |
+
|
1426 |
+
if st.button("Generate Visualization"):
|
1427 |
+
st.write(f"### Histogram of {column}")
|
1428 |
+
fig = px.histogram(df, x=column, nbins=bins, title=f"Distribution of {column}")
|
1429 |
+
st.plotly_chart(fig)
|
1430 |
+
|
1431 |
+
elif chart_type == "Pie Chart":
|
1432 |
+
column = st.selectbox("Category Column", all_cols)
|
1433 |
+
value_col = st.selectbox("Value Column", numeric_cols)
|
1434 |
+
|
1435 |
+
if st.button("Generate Visualization"):
|
1436 |
+
# Aggregate the data if needed
|
1437 |
+
pie_data = df.groupby(column)[value_col].sum().reset_index()
|
1438 |
+
st.write(f"### Pie Chart: {value_col} by {column}")
|
1439 |
+
fig = px.pie(pie_data, names=column, values=value_col,
|
1440 |
+
title=f"{value_col} by {column}")
|
1441 |
+
st.plotly_chart(fig)
|
1442 |
+
else:
|
1443 |
+
st.info("Load a table from BigQuery or upload a CSV file first")
|
1444 |
|
1445 |
elif selected == "About":
|
1446 |
st.markdown("## About This App")
|
|
|
1464 |
|
1465 |
st.info("Note: Make sure your Google Cloud credentials are properly set up to use this application.")
|
1466 |
|
1467 |
+
# At the end of the main function, add this footer
|
1468 |
+
# Add footer with attribution
|
1469 |
+
st.markdown("""
|
1470 |
+
<div style='position: fixed; bottom: 0; width: 100%; background-color: #f8f9fa;
|
1471 |
+
text-align: center; padding: 10px; border-top: 1px solid #e9ecef;'>
|
1472 |
+
<p style='color: #6c757d; font-size: 14px;'>
|
1473 |
+
Powered by Google Cloud with additional tools
|
1474 |
+
</p>
|
1475 |
+
</div>
|
1476 |
+
""", unsafe_allow_html=True)
|
1477 |
+
|
1478 |
if __name__ == "__main__":
|
1479 |
# Use GOOGLE_CREDENTIALS directly - no need for file or GOOGLE_APPLICATION_CREDENTIALS
|
1480 |
try:
|