meteor_detect/demos/star_chart_demo.rs

518 lines
20 KiB
Rust

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, DisplayBackendType, create_optimal_display};
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("index-4100/index-4119.fits".to_string());
vec.push("index-4100/index-4117.fits".to_string());
vec.push("index-4100/index-4118.fits".to_string());
config.star_chart = StarChartOptions {
enabled: true, // Enable star chart for testing
solve_field_path: "/usr/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();
let display_config = DisplayConfig {
display_scale: 1.0, // Start with full scale
frame_skip: 2, // Skip every other frame
async_rendering: false,
max_render_time_ms: 33, // ~30 FPS max
};
// Create optimal display backend (temporarily force OpenCV for debugging)
println!("Creating display backend...");
let mut display = create_optimal_display(display_config, DisplayBackendType::OpenCV);
// Check which backend was selected
let stats = display.get_stats();
println!("Selected display backend: {}", stats.backend_name);
let window_name = "Star Chart Solving Demo";
println!("Creating window: {}", window_name);
display.create_window(window_name)?;
println!("Resizing window...");
display.resize_window(window_name, 1280, 720)?;
println!("Display setup complete!");
// 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.as_ref())?;
// Display frame using optimized display system
if frame_count <= 5 {
let current_stats = display.get_stats();
println!("Displaying frame {} using backend: {}", frame_count, current_stats.backend_name);
}
if let Err(e) = display.show_frame(window_name, &display_frame) {
println!("Display error on frame {}: {}", frame_count, e);
if frame_count <= 3 {
// For first few frames, provide more detailed error info
eprintln!("Detailed error: {:?}", e);
}
} else if frame_count <= 5 {
println!("Frame {} displayed successfully", frame_count);
}
// 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
println!("Display scale increased (GStreamer backend doesn't support runtime scale changes)");
}
'-' => {
// Decrease display scale
println!("Display scale decreased (GStreamer backend doesn't support runtime scale changes)");
}
'f' => {
// Toggle frame skipping
println!("Frame skipping toggled (GStreamer backend doesn't support runtime frame skip changes)");
}
'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);
println!("Display backend optimized for hardware acceleration");
}
'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<chrono::Utc>,
star_chart_enabled: bool,
show_stars: bool,
show_constellations: bool,
show_ngc_objects: bool,
display: &dyn DisplayBackend,
) -> 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 perf_text = format!(
"Performance: {:.1}ms/frame | Backend: {} | Frames: {}/{}",
stats.avg_render_time_ms,
stats.backend_name,
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(())
}