use anyhow::{Context, Result}; use chrono::Utc; use opencv::{core, highgui, imgcodecs, imgproc, prelude::*}; use std::fs; use std::io::{self, Write}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use tokio; // Import modules from the project use meteor_detect::camera::{CameraController, CameraSettings, ExposureMode, Frame, Resolution}; use meteor_detect::config::{load_config, Config}; use meteor_detect::display::{DisplayBackend, DisplayConfig, OpenCVDisplay}; use meteor_detect::gps::{CameraOrientation, GeoPosition, GpsStatus}; use meteor_detect::overlay::star_chart::{StarChart, StarChartOptions}; use meteor_detect::utils::memory_monitor::{estimate_mat_size, get_system_memory_usage}; use env_logger; /// Star Chart Demo Program /// This demo shows how to use astrometry.net for star chart solving and overlay on video frames #[tokio::main] async fn main() -> Result<()> { std::env::set_var("RUST_LOG", "debug"); env_logger::init(); println!("*** Star Chart Solving Demo ***"); println!("This demo shows how to use astrometry.net to solve star charts and overlay them on video"); println!("Press 'q' to exit"); println!("Press 's' to manually trigger star chart solving"); println!("Press '1' to toggle stars display"); println!("Press '2' to toggle constellations display"); println!("Press '3' to toggle NGC objects display"); println!("Press 'd' to toggle all star chart display"); println!("Press '+' to increase display scale, '-' to decrease"); println!("Press 'f' to toggle frame skipping"); println!("Press 'p' to show performance stats"); println!("Press 'm' to show detailed memory analysis"); println!(); let file_path = "meteor.mov"; // Check if the file exists if !std::path::Path::new(file_path).exists() { println!("Error: File '{}' does not exist", file_path); return Ok(()); } // Create configuration let mut config = Config::default(); config.log_level = "debug".to_string(); // Change log level to debug for more detailed output // Set file input mode config.camera.file_input_mode = true; config.camera.input_file_path = file_path.to_string(); config.camera.loop_video = true; // Configure star chart solving parameters let mut vec = Vec::new(); vec.push("/Users/grabbit/Project/astrometry/index-4100/index-4119.fits".to_string()); vec.push("/Users/grabbit/Project/astrometry/index-4100/index-4117.fits".to_string()); vec.push("/Users/grabbit/Project/astrometry/index-4100/index-4118.fits".to_string()); config.star_chart = StarChartOptions { enabled: true, // Enable star chart for testing solve_field_path: "/opt/homebrew/bin/solve-field".to_string(), // index_path: "/Users/grabbit/Project/astrometry/index-4100".to_string(), index_path: "".to_string(), index_file: Some(vec), update_frequency: 5.0, // Update every 60 seconds (slower for large images) star_color: (0, 255, 0, 255), // Green constellation_color: (0, 180, 0, 180), // Semi-transparent green ngc_color: (255, 100, 0, 200), // Orange for NGC objects star_size: 3, line_thickness: 1, ngc_size: 5, working_dir: "/tmp/astrometry".to_string(), show_stars: true, show_star_names: true, show_ngc_names: true, show_constellations: true, show_ngc_objects: true, min_ngc_size: 5.0, min_ngc_pixel_percent: 0.005, // 0.5% of image width - good balance for demo index_scale_range: (70.0, 90.0), // Wider range in arcsec per pixel max_solve_time: 60, // Wait max 60 seconds for large images hint_timeout_minutes: 2, camera_calibration: None, }; // Ensure working directory exists let working_dir = std::path::Path::new(&config.star_chart.working_dir); if !working_dir.exists() { fs::create_dir_all(working_dir)?; } // Initialize camera controller println!("Initializing video file: {}", file_path); let mut camera_controller = CameraController::new(&config).await.unwrap(); camera_controller.initialize().await.unwrap(); // Create simulated GPS data - set a typical observation location (e.g., Beijing Observatory) let gps_status = Arc::new(Mutex::new(GpsStatus { position: GeoPosition { latitude: 40.0, // Beijing latitude ~40 degrees longitude: 116.4, // Beijing longitude ~116.4 degrees altitude: 50.0, // Altitude ~50 meters }, satellites: 8, timestamp: Utc::now(), sync_status: "FullSync".to_string(), time_accuracy_ms: 1.0, camera_orientation: CameraOrientation { azimuth: 0.0, // Facing north elevation: 90.0, // Vertical upward }, })); // Create star chart overlay println!("Initializing star chart solving component..."); let mut star_chart = StarChart::new(config.star_chart.clone(), gps_status.clone()) .await .unwrap(); // Start camera capture println!("Starting video playback..."); camera_controller.start_capture().await.unwrap(); // Get frame subscription let mut frame_rx = camera_controller.subscribe_to_frames(); // Create optimized display system let display_config = DisplayConfig { display_scale: 0.5, // Start with 50% scale for better performance frame_skip: 2, // Skip every other frame async_rendering: false, max_render_time_ms: 33, // ~30 FPS max }; let mut display = OpenCVDisplay::new(display_config); let window_name = "Star Chart Solving Demo"; display.create_window(window_name)?; display.resize_window(window_name, 1280, 720)?; // Status variables let mut show_star_chart = true; let mut show_stars = true; let mut show_constellations = true; let mut show_ngc_objects = true; let mut frame_count = 0; let mut last_manual_solve_time = Utc::now() - chrono::Duration::seconds(60); // Initially set to past time let mut last_memory_check = Instant::now(); // Frame processing loop println!("Entering frame processing loop"); while let Ok(frame) = frame_rx.recv().await { // println!("Attempting to receive frame"); frame_count += 1; // println!("Frame received: {}", frame_count); // Use frame directly for display to avoid unnecessary clone let mut display_frame = frame.mat; // Apply star chart overlay if show_star_chart { // Update star chart options based on current toggles let mut options = config.star_chart.clone(); options.show_stars = show_stars; options.show_constellations = show_constellations; options.show_ngc_objects = show_ngc_objects; star_chart.set_options(options); // println!("Applying star chart overlay for frame {}", frame_count); if let Err(e) = star_chart.apply(&mut display_frame, frame.timestamp).await { println!("Star chart application error: {}", e); } // println!("Finished applying star chart overlay for frame {}", frame_count); } // Add information overlay add_info_overlay(&mut display_frame, frame_count, frame.timestamp, show_star_chart, show_stars, show_constellations, show_ngc_objects, &display)?; // Display frame using optimized display system if let Err(e) = display.show_frame(window_name, &display_frame) { println!("Display error: {}", e); } // Periodic memory check (every 5 seconds) if last_memory_check.elapsed() > Duration::from_secs(5) { let stats = display.get_stats(); let frame_size = estimate_mat_size(&display_frame); println!("=== Main Loop Memory Check ==="); println!("Frame count: {}", frame_count); println!("Current display_frame size: {:.2} MB", frame_size as f64 / 1_000_000.0); println!("Display_frame dimensions: {}x{}", display_frame.cols(), display_frame.rows()); println!("Display stats - Frames: {}/{} (displayed/total), Avg render: {:.2}ms", stats.frames_displayed, stats.frames_displayed + stats.frames_dropped, stats.avg_render_time_ms); // Check if display_frame is growing if frame_size > 20_000_000 { // > 20MB println!("⚠️ WARNING: Display frame unusually large: {:.2}MB", frame_size as f64 / 1_000_000.0); } // System memory check if let Some((used, free)) = get_system_memory_usage() { let total = used + free; let used_gb = used as f64 / 1_000_000_000.0; let used_percent = (used as f64 / total as f64) * 100.0; println!("System memory: {:.2}GB used, {:.2}GB free ({:.1}% used)", used_gb, free as f64 / 1_000_000_000.0, used_percent); if used > 8_000_000_000 { // > 8GB println!("⚠️ WARNING: High memory usage detected!"); } // Track memory growth static mut LAST_MEMORY_USAGE: u64 = 0; unsafe { if LAST_MEMORY_USAGE > 0 { let growth = (used as i64) - (LAST_MEMORY_USAGE as i64); if growth > 100_000_000 { // > 100MB growth println!("🚨 MEMORY GROWTH DETECTED: +{:.2}MB since last check", growth as f64 / 1_000_000.0); } } LAST_MEMORY_USAGE = used; } } last_memory_check = Instant::now(); } // Check keys (non-blocking) let key = display.poll_key(1)?; match key as u8 as char { 'q' => { println!("Exiting..."); break; } 's' => { // Manually trigger star chart solving let now = Utc::now(); let elapsed = now.signed_duration_since(last_manual_solve_time); if elapsed.num_seconds() >= 10 { // At least 10 seconds interval println!("Manually triggering star chart solving..."); // We don't directly call star chart solving here, as it's handled in the background_thread // But we can "trick" the system into doing a new solve immediately by updating the last solve time last_manual_solve_time = now; // You can also save the current frame for analysis let save_path = format!("/tmp/astrometry/manual_solve_{}.jpg", now.timestamp()); imgcodecs::imwrite(&save_path, &display_frame, &core::Vector::new())?; println!("Current frame saved to: {}", save_path); } else { println!("Please wait at least 10 seconds before manually triggering solving"); } } '1' => { // Toggle stars display show_stars = !show_stars; println!( "Stars display: {}", if show_stars { "ON" } else { "OFF" } ); } '2' => { // Toggle constellations display show_constellations = !show_constellations; println!( "Constellations display: {}", if show_constellations { "ON" } else { "OFF" } ); } '3' => { // Toggle NGC objects display show_ngc_objects = !show_ngc_objects; println!( "NGC objects display: {}", if show_ngc_objects { "ON" } else { "OFF" } ); } 'd' => { // Toggle all star chart display show_star_chart = !show_star_chart; println!( "Star chart display: {}", if show_star_chart { "ON" } else { "OFF" } ); } '+' | '=' => { // Increase display scale let mut config = *display.config(); // Copy instead of clone config.display_scale = (config.display_scale + 0.1).min(1.0); display.set_config(config); println!("Display scale: {:.1}x", config.display_scale); } '-' => { // Decrease display scale let mut config = *display.config(); // Copy instead of clone config.display_scale = (config.display_scale - 0.1).max(0.1); display.set_config(config); println!("Display scale: {:.1}x", config.display_scale); } 'f' => { // Toggle frame skipping let mut config = *display.config(); // Copy instead of clone config.frame_skip = if config.frame_skip == 1 { 2 } else { 1 }; display.set_config(config); println!("Frame skip: every {} frame(s)", config.frame_skip); } 'p' => { // Show performance stats let stats = display.get_stats(); println!("=== Performance Stats ==="); println!("Backend: {}", stats.backend_name); println!("Frames displayed: {}", stats.frames_displayed); println!("Frames dropped: {}", stats.frames_dropped); println!("Avg render time: {:.2}ms", stats.avg_render_time_ms); let config = display.config(); println!("Display config: scale={:.1}x, skip={}", config.display_scale, config.frame_skip); } 'm' => { // Show detailed memory analysis println!("=== Detailed Memory Analysis ==="); let frame_size = estimate_mat_size(&display_frame); println!("Current display frame: {:.2} MB", frame_size as f64 / 1_000_000.0); if let Some((used, free)) = get_system_memory_usage() { let total = used + free; println!("System Memory:"); println!(" Total: {:.2} GB", total as f64 / 1_000_000_000.0); println!(" Used: {:.2} GB ({:.1}%)", used as f64 / 1_000_000_000.0, (used as f64 / total as f64) * 100.0); println!(" Free: {:.2} GB ({:.1}%)", free as f64 / 1_000_000_000.0, (free as f64 / total as f64) * 100.0); } // Force garbage collection (Rust doesn't have manual GC, but we can hint) println!("Suggesting garbage collection..."); std::hint::black_box(vec![0u8; 1]); // Small allocation to trigger GC heuristics } _ => {} } // println!("Finished processing frame {}", frame_count); } println!("Exiting frame processing loop"); // Cleanup println!("Shutting down..."); camera_controller.stop_capture().await.unwrap(); star_chart.shutdown().await.unwrap(); display.destroy_window(window_name)?; // Show final performance stats let final_stats = display.get_stats(); println!("=== Final Performance Stats ==="); println!("Total frames displayed: {}", final_stats.frames_displayed); println!("Total frames dropped: {}", final_stats.frames_dropped); println!("Final avg render time: {:.2}ms", final_stats.avg_render_time_ms); println!("Demo successfully completed"); Ok(()) } /// Add information overlay to video frame fn add_info_overlay( frame: &mut core::Mat, frame_count: u32, timestamp: chrono::DateTime, star_chart_enabled: bool, show_stars: bool, show_constellations: bool, show_ngc_objects: bool, display: &OpenCVDisplay, ) -> Result<()> { // Create overlay area parameters let overlay_height = 120; // Increased height for more information let width = frame.cols(); // Ensure height doesn't exceed frame height let overlay_height = std::cmp::min(overlay_height, frame.rows()); // Create dark semi-transparent background for top area let bg_rect = core::Rect::new(0, 0, width, overlay_height); let bg_color = core::Scalar::new(0.0, 0.0, 0.0, 0.0); // Create a temporary copy frame to store original area let original_roi = frame.roi(bg_rect)?.t().unwrap().to_mat().unwrap().clone(); // Draw semi-transparent rectangle on original frame imgproc::rectangle( frame, bg_rect, bg_color, -1, // Fill imgproc::LINE_8, 0 )?; // Add text directly on frame let white = core::Scalar::new(255.0, 255.0, 255.0, 0.0); let green = core::Scalar::new(0.0, 255.0, 0.0, 0.0); // Frame count and timestamp let time_text = format!( "Frame: {} | Time: {}", frame_count, timestamp.format("%Y-%m-%d %H:%M:%S%.3f") ); imgproc::put_text( frame, &time_text, core::Point::new(10, 20), imgproc::FONT_HERSHEY_SIMPLEX, 0.5, white, 1, imgproc::LINE_AA, false, )?; // Star chart status let star_chart_text = format!( "Star Chart: {} | Stars: {} | Constellations: {} | NGC: {}", if star_chart_enabled { "ON" } else { "OFF" }, if show_stars { "ON" } else { "OFF" }, if show_constellations { "ON" } else { "OFF" }, if show_ngc_objects { "ON" } else { "OFF" } ); imgproc::put_text( frame, &star_chart_text, core::Point::new(10, 40), imgproc::FONT_HERSHEY_SIMPLEX, 0.5, green, 1, imgproc::LINE_AA, false, )?; // Controls information let controls_text = "Controls: 'S'=Solve | 'D'=Toggle All | '1'=Stars | '2'=Constellations | '3'=NGC | 'Q'=Exit"; imgproc::put_text( frame, controls_text, core::Point::new(10, 60), imgproc::FONT_HERSHEY_SIMPLEX, 0.5, white, 1, imgproc::LINE_AA, false, )?; // File info and description let help_text = "This demo shows real-time star chart solving and overlay functionality"; imgproc::put_text( frame, help_text, core::Point::new(10, 80), imgproc::FONT_HERSHEY_SIMPLEX, 0.5, white, 1, imgproc::LINE_AA, false, )?; // Performance and optimization info let stats = display.get_stats(); let config = display.config(); let perf_text = format!( "Performance: {:.1}ms/frame | Scale: {:.1}x | Skip: {} | Frames: {}/{}", stats.avg_render_time_ms, config.display_scale, config.frame_skip, stats.frames_displayed, stats.frames_displayed + stats.frames_dropped ); imgproc::put_text( frame, &perf_text, core::Point::new(10, 100), imgproc::FONT_HERSHEY_SIMPLEX, 0.4, green, 1, imgproc::LINE_AA, false, )?; // Create a new frame to store final result (blend of original area and new content) let mut roi_dest = frame.roi_mut(bg_rect)?; // Blend original area and overlay // core::add_weighted(&original_roi, 0.3, &roi_dest.clone_pointee(), 0.7, 0.0, &mut roi_dest, -1)?; Ok(()) }