518 lines
20 KiB
Rust
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(())
|
|
}
|