""" MotionScope Pro — Streamlit front-end Run with: streamlit run app.py """ import tempfile import os import cv2 import numpy as np import streamlit as st from detector import MovementDetector, DetectionConfig, DetectionMode # --------------------------------------------------------------------------- # Page config # --------------------------------------------------------------------------- st.set_page_config( page_title="MotionScope Pro", page_icon="🎥", layout="wide", initial_sidebar_state="expanded", ) # --------------------------------------------------------------------------- # Custom CSS — dark, polished look # --------------------------------------------------------------------------- st.markdown( """ """, unsafe_allow_html=True, ) # --------------------------------------------------------------------------- # Hero header # --------------------------------------------------------------------------- st.markdown( """

🎥 MotionScope Pro

Advanced Movement Detection — Hand Tracking & Motion Analysis

""", unsafe_allow_html=True, ) # Feature badges st.markdown( """
🖐️ Hand Tracking 🚗 Motion Detection ⚡ Combined Mode 📹 Video Upload 📷 Webcam Snapshots
""", unsafe_allow_html=True, ) # --------------------------------------------------------------------------- # Sidebar — settings # --------------------------------------------------------------------------- with st.sidebar: st.markdown("## ⚙️ Detection Settings") mode_label = st.selectbox( "Detection Mode", options=[m.value for m in DetectionMode], index=0, help="Choose what the detector should look for.", ) mode = DetectionMode(mode_label) st.markdown("---") st.markdown("### 🔧 Motion Parameters") motion_threshold = st.slider( "Motion threshold", min_value=50, max_value=255, value=180, step=5, help="Higher → less sensitive (ignores faint motion).", ) min_contour_area = st.slider( "Min object area (px²)", min_value=100, max_value=10000, value=1000, step=100, help="Ignore contours smaller than this area.", ) st.markdown("---") st.markdown("### 🖐️ Hand Parameters") max_hands = st.slider("Max hands to detect", 1, 4, 2) det_confidence = st.slider( "Detection confidence", 0.1, 1.0, 0.5, 0.05, ) track_confidence = st.slider( "Tracking confidence", 0.1, 1.0, 0.5, 0.05, ) st.markdown("---") st.markdown( "Built with OpenCV · MediaPipe · Streamlit", unsafe_allow_html=True, ) # Build config from sidebar values config = DetectionConfig( min_detection_confidence=det_confidence, min_tracking_confidence=track_confidence, max_num_hands=max_hands, motion_threshold=motion_threshold, min_contour_area=min_contour_area, ) # --------------------------------------------------------------------------- # Cached detector (rebuilt when config changes) # --------------------------------------------------------------------------- @st.cache_resource def get_detector(): return MovementDetector() detector = get_detector() detector.rebuild(config) # --------------------------------------------------------------------------- # Tabs — Video Upload | Webcam Snapshot # --------------------------------------------------------------------------- tab_video, tab_webcam = st.tabs(["📹 Video Upload", "📷 Webcam Snapshot"]) # ======================== VIDEO UPLOAD TAB ============================== with tab_video: uploaded = st.file_uploader( "Upload a video file", type=["mp4", "avi", "mov", "mkv"], help="Supported formats: MP4, AVI, MOV, MKV", ) if uploaded is not None: # Save upload to a temp file tfile = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") tfile.write(uploaded.read()) tfile.flush() input_path = tfile.name # Show the original video with st.expander("🎬 Original video", expanded=False): st.video(input_path) # Process button if st.button("🚀 Process Video", type="primary", use_container_width=True): output_path = os.path.join(tempfile.gettempdir(), "motionscope_output.mp4") progress_bar = st.progress(0, text="Processing…") frame_placeholder = st.empty() metrics_placeholder = st.empty() total_objects = 0 frame_num = 0 try: for display_frame, result_path, progress in detector.process_video( input_path, mode=mode, output_path=output_path, ): if display_frame is not None: frame_num += 1 # Show every 4th frame for speed if frame_num % 4 == 0 or progress >= 1.0: frame_placeholder.image( display_frame, caption=f"Frame {detector.frame_count}", use_container_width=True, ) progress_bar.progress( progress, text=f"Processing… {int(progress * 100)}%", ) if result_path is not None: progress_bar.progress(1.0, text="✅ Done!") st.success( f"Processed **{detector.frame_count}** frames successfully!" ) # Metrics row col1, col2, col3 = st.columns(3) col1.metric("Total Frames", detector.frame_count) col2.metric("Mode", mode.value) col3.metric("Status", "✅ Complete") # Download button with open(result_path, "rb") as f: st.download_button( "⬇️ Download Processed Video", data=f, file_name="motionscope_output.mp4", mime="video/mp4", use_container_width=True, ) except Exception as e: st.error(f"❌ Error during processing: {e}") finally: # Cleanup temp input try: os.unlink(input_path) except OSError: pass else: # Empty state st.markdown( """

📹

Upload a video above to get started

""", unsafe_allow_html=True, ) # ======================== WEBCAM SNAPSHOT TAB =========================== with tab_webcam: st.markdown( "Take a photo with your webcam and the detector will process it instantly." ) if mode == DetectionMode.MOTION_DETECTION: st.warning("⚠️ **Motion Detection** requires a video stream to compare frames. For a single photo, use **Hand Tracking** or **Combined** mode.") camera_input = st.camera_input("📷 Take a photo") if camera_input is not None: # Decode the image file_bytes = np.frombuffer(camera_input.getvalue(), dtype=np.uint8) img_bgr = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) if img_bgr is not None: # Flip for mirror effect img_bgr = cv2.flip(img_bgr, 1) # Process processed_bgr = detector.process_frame(img_bgr, mode) processed_rgb = cv2.cvtColor(processed_bgr, cv2.COLOR_BGR2RGB) col_orig, col_proc = st.columns(2) with col_orig: st.markdown("**Original**") original_rgb = cv2.cvtColor( cv2.flip(img_bgr, 1), cv2.COLOR_BGR2RGB # undo our flip for display ) st.image(original_rgb, use_container_width=True) with col_proc: st.markdown("**Processed**") st.image(processed_rgb, use_container_width=True) # Download processed image _, buf = cv2.imencode(".jpg", processed_bgr) st.download_button( "⬇️ Download Processed Image", data=buf.tobytes(), file_name="motionscope_snapshot.jpg", mime="image/jpeg", use_container_width=True, ) else: st.error("Could not decode the captured image.") else: st.markdown( """

📷

Click the camera button above to capture a snapshot

""", unsafe_allow_html=True, )