feat: 增加星系名称的显示;优化星点;
This commit is contained in:
parent
9326e15366
commit
9733640728
@ -21,7 +21,7 @@ use env_logger;
|
||||
/// 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");
|
||||
// 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");
|
||||
@ -126,9 +126,8 @@ async fn main() -> Result<()> {
|
||||
// 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
|
||||
display_scale: 1.0, // 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
|
||||
|
||||
@ -17,6 +17,7 @@ use tokio::time;
|
||||
use crate::gps::{GeoPosition, GpsStatus};
|
||||
use crate::camera::{Frame};
|
||||
use crate::utils::memory_monitor::estimate_mat_size;
|
||||
use crate::utils::opencv_compat;
|
||||
|
||||
/// Configuration options for the star chart overlay
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -177,6 +178,17 @@ struct ConstellationLine {
|
||||
pixel_positions: Option<(core::Point, core::Point)>,
|
||||
}
|
||||
|
||||
/// Constellation label information for rendering constellation names
|
||||
#[derive(Debug, Clone)]
|
||||
struct ConstellationLabel {
|
||||
/// Constellation name
|
||||
name: String,
|
||||
/// Centroid position in RA/Dec coordinates
|
||||
centroid_radec: (f64, f64),
|
||||
/// Centroid position in pixel coordinates (if mapped)
|
||||
centroid_pixel: Option<core::Point>,
|
||||
}
|
||||
|
||||
/// Constellation data loaded from files
|
||||
#[derive(Debug, Clone)]
|
||||
struct ConstellationData {
|
||||
@ -213,11 +225,12 @@ struct NgcCatalogEntry {
|
||||
v_mag: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct StarChartState {
|
||||
latest_hint: Option<SolutionHint>,
|
||||
stars: Vec<Star>,
|
||||
constellation_lines: Vec<ConstellationLine>,
|
||||
constellation_labels: Vec<ConstellationLabel>,
|
||||
ngc_objects: Vec<NgcObject>,
|
||||
// Static catalog data (loaded once)
|
||||
constellation_catalog: Vec<ConstellationData>,
|
||||
@ -231,6 +244,26 @@ struct StarChartState {
|
||||
solving_in_progress: bool,
|
||||
}
|
||||
|
||||
impl Default for StarChartState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
latest_hint: None,
|
||||
stars: Vec::new(),
|
||||
constellation_lines: Vec::new(),
|
||||
constellation_labels: Vec::new(),
|
||||
ngc_objects: Vec::new(),
|
||||
constellation_catalog: Vec::new(),
|
||||
ngc_catalog: Vec::new(),
|
||||
current_wcs_path: None,
|
||||
current_axy_path: None,
|
||||
hint_timestamp_for_current_stars: None,
|
||||
hint_timestamp_for_current_ngc: None,
|
||||
hint_timestamp_for_current_constellations: None,
|
||||
solving_in_progress: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message types for the astrometry solver thread
|
||||
enum SolverMessage {
|
||||
/// Request to process a new frame
|
||||
@ -1228,7 +1261,7 @@ impl StarChart {
|
||||
new_hint.ra, new_hint.dec, new_hint.scale);
|
||||
let mut state = state.lock().unwrap();
|
||||
state.latest_hint = Some(new_hint);
|
||||
state.current_wcs_path = Some(wcs_path);
|
||||
state.current_wcs_path = Some(wcs_path.clone());
|
||||
state.current_axy_path = Some(axy_path);
|
||||
last_solve_time = now;
|
||||
}
|
||||
@ -1311,7 +1344,7 @@ impl StarChart {
|
||||
|
||||
// Use previous hint if available
|
||||
if let Some(hint) = previous_hint {
|
||||
info!("Using previous solution as hint: RA={}, Dec={}, Scale={}",
|
||||
debug!("Using previous solution as hint: RA={}, Dec={}, Scale={}",
|
||||
hint.ra, hint.dec, hint.scale);
|
||||
|
||||
// Provide RA/Dec center hint
|
||||
@ -1327,7 +1360,7 @@ impl StarChart {
|
||||
command.arg("--scale-low").arg(scale_low.to_string());
|
||||
command.arg("--scale-high").arg(scale_high.to_string());
|
||||
} else {
|
||||
info!("No previous hint available, using wider search parameters");
|
||||
debug!("No previous hint available, using wider search parameters");
|
||||
// For the first solve, use a much wider scale range to increase chances of success
|
||||
command.arg("--scale-low").arg("70.0");
|
||||
command.arg("--scale-high").arg("90.0"); // Very wide range for first solve
|
||||
@ -1384,11 +1417,11 @@ impl StarChart {
|
||||
Ok(output_result) => {
|
||||
match output_result {
|
||||
Ok(output) => {
|
||||
// debug!("solve-field command finished with status: {}", output.status);
|
||||
debug!("solve-field command finished with status: {}", output.status);
|
||||
let stdout_str = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr_str = String::from_utf8_lossy(&output.stderr);
|
||||
// debug!("solve-field stdout: {}", stdout_str);
|
||||
// debug!("solve-field stderr: {}", stderr_str);
|
||||
debug!("solve-field stdout: {}", stdout_str);
|
||||
debug!("solve-field stderr: {}", stderr_str);
|
||||
|
||||
// The real test for success is if the WCS file was created.
|
||||
let wcs_file = input_file.with_extension("wcs");
|
||||
@ -1516,7 +1549,7 @@ impl StarChart {
|
||||
10.0 // Default radius in degrees (more reasonable for typical camera FOV)
|
||||
};
|
||||
|
||||
info!("🎯 PARSED WCS SOLUTION: RA={:.6}°, Dec={:.6}°, Scale={:.3} arcsec/px, Radius={:.3}°",
|
||||
info!("🎯 PARSED WCS SOLUTION: RA={:.6}°, Dec={:.6}°, Scale={:.3} arcsec/px, Radius={:.3}°",
|
||||
ra, dec, scale, radius);
|
||||
|
||||
if ra == 0.0 && dec == 0.0 {
|
||||
@ -1686,6 +1719,7 @@ impl StarChart {
|
||||
// info!("Drawing {} constellation lines", state.constellation_lines.len());
|
||||
for line in &state.constellation_lines {
|
||||
if let Some((start, end)) = line.pixel_positions {
|
||||
// Draw constellation line
|
||||
imgproc::line(
|
||||
frame,
|
||||
start,
|
||||
@ -1695,6 +1729,88 @@ impl StarChart {
|
||||
imgproc::LINE_AA,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Draw small circles at star positions (line endpoints)
|
||||
let star_radius = 3; // Small radius for star circles
|
||||
let star_thickness = -1; // Filled circle
|
||||
|
||||
// Draw circle at start point
|
||||
imgproc::circle(
|
||||
frame,
|
||||
start,
|
||||
star_radius,
|
||||
constellation_color,
|
||||
star_thickness,
|
||||
imgproc::LINE_AA,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Draw circle at end point
|
||||
imgproc::circle(
|
||||
frame,
|
||||
end,
|
||||
star_radius,
|
||||
constellation_color,
|
||||
star_thickness,
|
||||
imgproc::LINE_AA,
|
||||
0,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw constellation labels
|
||||
for label in &state.constellation_labels {
|
||||
if let Some(centroid_pixel) = label.centroid_pixel {
|
||||
// Draw constellation name at centroid position
|
||||
let font = imgproc::FONT_HERSHEY_SIMPLEX;
|
||||
let font_scale = 0.6;
|
||||
let thickness = 1;
|
||||
|
||||
// Calculate text size to center it
|
||||
let mut baseline = 0;
|
||||
let text_size = imgproc::get_text_size(&label.name, font, font_scale, thickness, &mut baseline)?;
|
||||
|
||||
// Position text centered on the centroid
|
||||
let text_position = core::Point::new(
|
||||
centroid_pixel.x - text_size.width / 2,
|
||||
centroid_pixel.y + text_size.height / 2,
|
||||
);
|
||||
|
||||
// Draw text with slight outline for better visibility
|
||||
let text_color = constellation_color;
|
||||
let outline_color = core::Scalar::new(0.0, 0.0, 0.0, 255.0); // Black outline
|
||||
|
||||
// Draw outline (slightly offset in each direction)
|
||||
for &(dx, dy) in &[(-1, -1), (-1, 1), (1, -1), (1, 1)] {
|
||||
let outline_pos = core::Point::new(text_position.x + dx, text_position.y + dy);
|
||||
imgproc::put_text(
|
||||
frame,
|
||||
&label.name,
|
||||
outline_pos,
|
||||
font,
|
||||
font_scale,
|
||||
outline_color,
|
||||
thickness + 1,
|
||||
imgproc::LINE_AA,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Draw main text
|
||||
imgproc::put_text(
|
||||
frame,
|
||||
&label.name,
|
||||
text_position,
|
||||
font,
|
||||
font_scale,
|
||||
text_color,
|
||||
thickness,
|
||||
imgproc::LINE_AA,
|
||||
false,
|
||||
)?;
|
||||
|
||||
debug!("Drew constellation label '{}' at pixel ({}, {})",
|
||||
label.name, centroid_pixel.x, centroid_pixel.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1715,41 +1831,18 @@ impl StarChart {
|
||||
state.stars.clear();
|
||||
state.constellation_lines.clear();
|
||||
|
||||
let mut fitsfile = FitsFile::open(&axy_path)
|
||||
.with_context(|| format!("Failed to open .axy FITS file at {:?}", axy_path))?;
|
||||
|
||||
// The star list is typically in the second HDU (index 1) for .axy files.
|
||||
let hdu = fitsfile.hdu(1)?;
|
||||
|
||||
// Read the coordinate and flux columns from the FITS table.
|
||||
let x_coords: Vec<f64> = hdu.read_col(&mut fitsfile, "X")
|
||||
.context("Failed to read 'X' column from .axy file")?;
|
||||
let y_coords: Vec<f64> = hdu.read_col(&mut fitsfile, "Y")
|
||||
.context("Failed to read 'Y' column from .axy file")?;
|
||||
|
||||
// FLUX is a good indicator of brightness.
|
||||
let flux_vals: Vec<f32> = hdu.read_col(&mut fitsfile, "FLUX")
|
||||
.context("Failed to read 'FLUX' column from .axy file")?;
|
||||
|
||||
for i in 0..x_coords.len() {
|
||||
// A simple, non-physical conversion from flux to magnitude for visualization.
|
||||
// Brighter objects have lower magnitude.
|
||||
let magnitude = if flux_vals[i] > 0.0 {
|
||||
-2.5 * flux_vals[i].log10()
|
||||
} else {
|
||||
99.0 // A very dim magnitude for objects with no or negative flux
|
||||
};
|
||||
|
||||
state.stars.push(Star {
|
||||
name: None, // .axy file doesn't contain star names
|
||||
ra: 0.0, // RA/Dec are in the .rdls file, not here
|
||||
dec: 0.0,
|
||||
magnitude,
|
||||
position: Some(core::Point::new(x_coords[i] as i32, y_coords[i] as i32)),
|
||||
});
|
||||
// Try to read .rdls file which contains RA/Dec coordinates
|
||||
let rdls_path = axy_path.with_extension("rdls");
|
||||
if rdls_path.exists() {
|
||||
debug!("Found .rdls file, reading RA/Dec coordinates");
|
||||
self.load_stars_from_rdls(state, &rdls_path, axy_path)?;
|
||||
} else {
|
||||
// Fallback: read from .axy file and convert pixel coordinates to RA/Dec using WCS
|
||||
debug!("No .rdls file found, converting pixel coordinates using WCS");
|
||||
self.load_stars_from_axy_with_wcs(state, axy_path)?;
|
||||
}
|
||||
|
||||
debug!("Loaded {} stars from {}.", state.stars.len(), axy_path.display());
|
||||
debug!("Loaded {} stars with RA/Dec coordinates.", state.stars.len());
|
||||
|
||||
// We are not processing constellations from this file.
|
||||
state.constellation_lines.clear();
|
||||
@ -1757,6 +1850,111 @@ impl StarChart {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load stars from .rdls file (contains RA/Dec coordinates)
|
||||
fn load_stars_from_rdls(&self, state: &mut MutexGuard<StarChartState>, rdls_path: &Path, axy_path: &Path) -> Result<()> {
|
||||
// Read pixel coordinates from .axy file
|
||||
let mut axy_fits = FitsFile::open(axy_path)?;
|
||||
let axy_hdu = axy_fits.hdu(1)?;
|
||||
let x_coords: Vec<f64> = axy_hdu.read_col(&mut axy_fits, "X")?;
|
||||
let y_coords: Vec<f64> = axy_hdu.read_col(&mut axy_fits, "Y")?;
|
||||
let flux_vals: Vec<f32> = axy_hdu.read_col(&mut axy_fits, "FLUX")?;
|
||||
|
||||
// Read RA/Dec coordinates from .rdls file
|
||||
let mut rdls_fits = FitsFile::open(rdls_path)?;
|
||||
let rdls_hdu = rdls_fits.hdu(1)?;
|
||||
let ra_coords: Vec<f64> = rdls_hdu.read_col(&mut rdls_fits, "RA")?;
|
||||
let dec_coords: Vec<f64> = rdls_hdu.read_col(&mut rdls_fits, "DEC")?;
|
||||
|
||||
let min_len = ra_coords.len().min(dec_coords.len()).min(x_coords.len()).min(flux_vals.len());
|
||||
|
||||
for i in 0..min_len {
|
||||
let magnitude = if flux_vals[i] > 0.0 {
|
||||
-2.5 * flux_vals[i].log10()
|
||||
} else {
|
||||
99.0
|
||||
};
|
||||
|
||||
state.stars.push(Star {
|
||||
name: None,
|
||||
ra: ra_coords[i],
|
||||
dec: dec_coords[i],
|
||||
magnitude,
|
||||
position: Some(core::Point::new(x_coords[i] as i32, y_coords[i] as i32)),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load stars from .axy file and convert pixel coordinates to RA/Dec using WCS
|
||||
fn load_stars_from_axy_with_wcs(&self, state: &mut MutexGuard<StarChartState>, axy_path: &Path) -> Result<()> {
|
||||
let mut fitsfile = FitsFile::open(axy_path)?;
|
||||
let hdu = fitsfile.hdu(1)?;
|
||||
|
||||
let x_coords: Vec<f64> = hdu.read_col(&mut fitsfile, "X")?;
|
||||
let y_coords: Vec<f64> = hdu.read_col(&mut fitsfile, "Y")?;
|
||||
let flux_vals: Vec<f32> = hdu.read_col(&mut fitsfile, "FLUX")?;
|
||||
|
||||
// Get WCS path for coordinate conversion
|
||||
let wcs_path_clone = state.current_wcs_path.clone();
|
||||
if let Some(wcs_path) = wcs_path_clone {
|
||||
for i in 0..x_coords.len() {
|
||||
let magnitude = if flux_vals[i] > 0.0 {
|
||||
-2.5 * flux_vals[i].log10()
|
||||
} else {
|
||||
99.0
|
||||
};
|
||||
|
||||
// Convert pixel coordinates to RA/Dec using WCS (reverse conversion)
|
||||
if let Ok((ra, dec)) = Self::pixel_to_radec(x_coords[i], y_coords[i], &wcs_path) {
|
||||
state.stars.push(Star {
|
||||
name: None,
|
||||
ra,
|
||||
dec,
|
||||
magnitude,
|
||||
position: Some(core::Point::new(x_coords[i] as i32, y_coords[i] as i32)),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("No WCS path available for pixel to RA/Dec conversion");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert pixel coordinates to RA/Dec using WCS (reverse transformation)
|
||||
fn pixel_to_radec(pixel_x: f64, pixel_y: f64, wcs_path: &Path) -> Result<(f64, f64)> {
|
||||
// This is a simplified reverse transformation
|
||||
// In a full implementation, you'd use proper WCS libraries
|
||||
let mut fits = FitsFile::open(wcs_path)?;
|
||||
let hdu = fits.primary_hdu()?;
|
||||
|
||||
let crval1 = hdu.read_key::<f64>(&mut fits, "CRVAL1").unwrap_or(0.0);
|
||||
let crval2 = hdu.read_key::<f64>(&mut fits, "CRVAL2").unwrap_or(0.0);
|
||||
let crpix1 = hdu.read_key::<f64>(&mut fits, "CRPIX1").unwrap_or(0.0);
|
||||
let crpix2 = hdu.read_key::<f64>(&mut fits, "CRPIX2").unwrap_or(0.0);
|
||||
|
||||
let cd1_1 = hdu.read_key::<f64>(&mut fits, "CD1_1").unwrap_or(0.0);
|
||||
let cd1_2 = hdu.read_key::<f64>(&mut fits, "CD1_2").unwrap_or(0.0);
|
||||
let cd2_1 = hdu.read_key::<f64>(&mut fits, "CD2_1").unwrap_or(0.0);
|
||||
let cd2_2 = hdu.read_key::<f64>(&mut fits, "CD2_2").unwrap_or(0.0);
|
||||
|
||||
// Calculate offset from reference pixel
|
||||
let delta_x = pixel_x - crpix1;
|
||||
let delta_y = pixel_y - crpix2;
|
||||
|
||||
// Apply CD matrix transformation
|
||||
let xi_deg = cd1_1 * delta_x + cd1_2 * delta_y;
|
||||
let eta_deg = cd2_1 * delta_x + cd2_2 * delta_y;
|
||||
|
||||
// Simple approximation for small angles (good enough for local field)
|
||||
let ra = crval1 + xi_deg;
|
||||
let dec = crval2 + eta_deg;
|
||||
|
||||
Ok((ra, dec))
|
||||
}
|
||||
|
||||
/// Update NGC objects for the current field of view
|
||||
fn update_ngc_objects(&self, state: &mut MutexGuard<StarChartState>, wcs_path: &Path) -> Result<()> {
|
||||
info!("update_ngc_objects called with WCS path: {:?}", wcs_path);
|
||||
@ -1820,7 +2018,7 @@ impl StarChart {
|
||||
within_fov_count += 1;
|
||||
|
||||
// Convert RA/Dec to pixel coordinates using WCS transformation
|
||||
match Self::radec_to_pixel(catalog_entry.ra, catalog_entry.dec, wcs_path, self.options.camera_calibration.as_ref()) {
|
||||
match Self::radec_to_pixel(catalog_entry.ra, catalog_entry.dec, wcs_path) {
|
||||
Ok(pixel_pos) => {
|
||||
state.ngc_objects.push(NgcObject {
|
||||
name: catalog_entry.name.clone(),
|
||||
@ -1842,7 +2040,7 @@ impl StarChart {
|
||||
}
|
||||
}
|
||||
|
||||
info!("📊 NGC processing summary: {} total objects, {} too small (>{:.1}'), {} within FOV, {} pixel conversion failed, {} final objects added",
|
||||
debug!(" NGC processing summary: {} total objects, {} too small (>{:.1}'), {} within FOV, {} pixel conversion failed, {} final objects added",
|
||||
total_processed, size_filtered_count, self.options.min_ngc_size,
|
||||
within_fov_count, pixel_conversion_failed_count, state.ngc_objects.len());
|
||||
|
||||
@ -1863,6 +2061,7 @@ impl StarChart {
|
||||
old_lines_count, catalog_size, estimated_catalog_memory as f64 / 1_000_000.0);
|
||||
|
||||
state.constellation_lines.clear();
|
||||
state.constellation_labels.clear();
|
||||
|
||||
if !wcs_path.exists() {
|
||||
warn!("WCS file not found at {:?}", wcs_path);
|
||||
@ -1905,11 +2104,11 @@ impl StarChart {
|
||||
|
||||
if start_distance <= radius * 1.5 || end_distance <= radius * 1.5 {
|
||||
// Convert both star positions to pixel coordinates
|
||||
let start_pixel = Self::radec_to_pixel(start_pos.0, start_pos.1, wcs_path, self.options.camera_calibration.as_ref()).ok();
|
||||
let end_pixel = Self::radec_to_pixel(end_pos.0, end_pos.1, wcs_path, self.options.camera_calibration.as_ref()).ok();
|
||||
let start_pixel = Self::radec_to_pixel(start_pos.0, start_pos.1, wcs_path).ok();
|
||||
let end_pixel = Self::radec_to_pixel(end_pos.0, end_pos.1, wcs_path).ok();
|
||||
|
||||
let pixel_positions = if let (Some(start), Some(end)) = (start_pixel, end_pixel) {
|
||||
info!("✅ Constellation {} line pixels: ({}, {}) to ({}, {})",
|
||||
debug!("✅ Constellation {} line pixels: ({}, {}) to ({}, {})",
|
||||
constellation.short_name, start.x, start.y, end.x, end.y);
|
||||
Some((start, end))
|
||||
} else {
|
||||
@ -1930,15 +2129,66 @@ impl StarChart {
|
||||
}
|
||||
|
||||
if visible_lines > 0 {
|
||||
info!("📊 Constellation {}: {} visible lines, {} pixel conversion failures",
|
||||
debug!("📊 Constellation {}: {} visible lines, {} pixel conversion failures",
|
||||
constellation.short_name, visible_lines, pixel_conversion_failures);
|
||||
|
||||
// Calculate constellation centroid from only the stars visible in the field of view
|
||||
let visible_stars = Self::get_visible_stars_in_field(&constellation.star_positions, center_ra, center_dec, radius);
|
||||
|
||||
if !visible_stars.is_empty() {
|
||||
let centroid = Self::calculate_constellation_centroid(&visible_stars);
|
||||
|
||||
// Convert centroid to pixel coordinates
|
||||
let centroid_pixel = Self::radec_to_pixel(centroid.0, centroid.1, wcs_path).ok();
|
||||
|
||||
// Add constellation label
|
||||
state.constellation_labels.push(ConstellationLabel {
|
||||
name: constellation.short_name.clone(),
|
||||
centroid_radec: centroid,
|
||||
centroid_pixel,
|
||||
});
|
||||
|
||||
debug!("✅ Added constellation label for {} at RA/Dec ({:.3}, {:.3}) from {} visible stars",
|
||||
constellation.short_name, centroid.0, centroid.1, visible_stars.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Updated {} constellation lines for current field", state.constellation_lines.len());
|
||||
debug!("Updated {} constellation labels for current field", state.constellation_labels.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get stars that are visible within the field of view
|
||||
fn get_visible_stars_in_field(star_positions: &[(f64, f64)], center_ra: f64, center_dec: f64, radius: f64) -> Vec<(f64, f64)> {
|
||||
star_positions
|
||||
.iter()
|
||||
.filter(|&&(ra, dec)| {
|
||||
let distance = Self::angular_distance(center_ra, center_dec, ra, dec);
|
||||
distance <= radius * 1.5 // Use same distance threshold as for constellation lines
|
||||
})
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Calculate centroid (center point) of a constellation from its star positions
|
||||
fn calculate_constellation_centroid(star_positions: &[(f64, f64)]) -> (f64, f64) {
|
||||
if star_positions.is_empty() {
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
|
||||
let mut sum_ra = 0.0;
|
||||
let mut sum_dec = 0.0;
|
||||
let count = star_positions.len() as f64;
|
||||
|
||||
for &(ra, dec) in star_positions {
|
||||
sum_ra += ra;
|
||||
sum_dec += dec;
|
||||
}
|
||||
|
||||
(sum_ra / count, sum_dec / count)
|
||||
}
|
||||
|
||||
/// Calculate angular distance between two points in degrees
|
||||
fn angular_distance(ra1: f64, dec1: f64, ra2: f64, dec2: f64) -> f64 {
|
||||
let d_ra = (ra2 - ra1).to_radians();
|
||||
@ -1948,7 +2198,7 @@ impl StarChart {
|
||||
}
|
||||
|
||||
/// Convert RA/Dec to pixel coordinates using WCS transformation with optional distortion correction
|
||||
fn radec_to_pixel(ra: f64, dec: f64, wcs_path: &Path, camera_cal: Option<&CameraCalibration>) -> Result<core::Point> {
|
||||
fn radec_to_pixel(ra: f64, dec: f64, wcs_path: &Path) -> Result<core::Point> {
|
||||
// For now, this is a simplified implementation
|
||||
// In a real implementation, this would use proper WCS transformation libraries
|
||||
// like wcslib or astropy equivalents in Rust
|
||||
@ -1973,10 +2223,10 @@ impl StarChart {
|
||||
let naxis1 = hdu.read_key::<i64>(&mut fits, "NAXIS1").unwrap_or(0);
|
||||
let naxis2 = hdu.read_key::<i64>(&mut fits, "NAXIS2").unwrap_or(0);
|
||||
|
||||
info!("🌟 WCS COORDINATE DEBUG for object at RA={:.6}°, Dec={:.6}°", ra, dec);
|
||||
info!("📐 WCS Reference: CRVAL=({:.6}, {:.6}), CRPIX=({:.2}, {:.2})", crval1, crval2, crpix1, crpix2);
|
||||
info!("🔢 CD Matrix: [{:.8}, {:.8}] [{:.8}, {:.8}]", cd1_1, cd1_2, cd2_1, cd2_2);
|
||||
info!("📏 Image dimensions: NAXIS1={}, NAXIS2={}", naxis1, naxis2);
|
||||
debug!("🌟 WCS COORDINATE DEBUG for object at RA={:.6}°, Dec={:.6}°", ra, dec);
|
||||
debug!("📐 WCS Reference: CRVAL=({:.6}, {:.6}), CRPIX=({:.2}, {:.2})", crval1, crval2, crpix1, crpix2);
|
||||
debug!("🔢 CD Matrix: [{:.8}, {:.8}] [{:.8}, {:.8}]", cd1_1, cd1_2, cd2_1, cd2_2);
|
||||
debug!("📏 Image dimensions: NAXIS1={}, NAXIS2={}", naxis1, naxis2);
|
||||
|
||||
// Validate CD matrix
|
||||
if cd1_1 == 0.0 && cd1_2 == 0.0 && cd2_1 == 0.0 && cd2_2 == 0.0 {
|
||||
@ -1989,8 +2239,8 @@ impl StarChart {
|
||||
let pc1_2 = hdu.read_key::<f64>(&mut fits, "PC1_2").unwrap_or(0.0);
|
||||
let pc2_1 = hdu.read_key::<f64>(&mut fits, "PC2_1").unwrap_or(0.0);
|
||||
let pc2_2 = hdu.read_key::<f64>(&mut fits, "PC2_2").unwrap_or(1.0);
|
||||
|
||||
info!("📊 Fallback parameters: CDELT=({:.8}, {:.8}), PC matrix=[{:.6}, {:.6}] [{:.6}, {:.6}]",
|
||||
|
||||
debug!("📊 Fallback parameters: CDELT=({:.8}, {:.8}), PC matrix=[{:.6}, {:.6}] [{:.6}, {:.6}]",
|
||||
cdelt1, cdelt2, pc1_1, pc1_2, pc2_1, pc2_2);
|
||||
|
||||
if cdelt1 != 0.0 || cdelt2 != 0.0 {
|
||||
@ -2021,8 +2271,8 @@ impl StarChart {
|
||||
delta_ra += 360.0;
|
||||
}
|
||||
let delta_ra_rad = delta_ra.to_radians();
|
||||
|
||||
info!("🔄 Angular differences: ΔRA={:.6}° ({:.6} rad), ΔDec={:.6}°",
|
||||
|
||||
debug!("🔄 Angular differences: ΔRA={:.6}° ({:.6} rad), ΔDec={:.6}°",
|
||||
delta_ra, delta_ra_rad, dec - crval2);
|
||||
|
||||
// Gnomonic projection formulas
|
||||
@ -2038,8 +2288,8 @@ impl StarChart {
|
||||
// Convert from radians to degrees for CD matrix
|
||||
let xi_deg = xi.to_degrees();
|
||||
let eta_deg = eta.to_degrees();
|
||||
|
||||
info!("🗺️ Gnomonic projection: xi={:.8}° ({:.6} rad), eta={:.8}° ({:.6} rad), denom={:.6}",
|
||||
|
||||
debug!("🗺️ Gnomonic projection: xi={:.8}° ({:.6} rad), eta={:.8}° ({:.6} rad), denom={:.6}",
|
||||
xi_deg, xi.to_degrees(), eta_deg, eta.to_degrees(), denom);
|
||||
|
||||
// Apply inverse CD matrix transformation to get pixel offsets
|
||||
@ -2053,9 +2303,9 @@ impl StarChart {
|
||||
let cd_inv_12 = -cd1_2 / det;
|
||||
let cd_inv_21 = -cd2_1 / det;
|
||||
let cd_inv_22 = cd1_1 / det;
|
||||
|
||||
info!("🔀 CD matrix determinant: {:.8e}", det);
|
||||
info!("🔀 Inverse CD matrix: [{:.8}, {:.8}] [{:.8}, {:.8}]",
|
||||
|
||||
debug!("🔀 CD matrix determinant: {:.8e}", det);
|
||||
debug!("🔀 Inverse CD matrix: [{:.8}, {:.8}] [{:.8}, {:.8}]",
|
||||
cd_inv_11, cd_inv_12, cd_inv_21, cd_inv_22);
|
||||
|
||||
let delta_x = cd_inv_11 * xi_deg + cd_inv_12 * eta_deg;
|
||||
@ -2064,8 +2314,8 @@ impl StarChart {
|
||||
// Convert to pixel coordinates
|
||||
let pixel_x = crpix1 + delta_x;
|
||||
let pixel_y = crpix2 + delta_y;
|
||||
|
||||
info!("📍 Pixel transformation: Δx={:.3}, Δy={:.3} -> final pixel=({:.1}, {:.1})",
|
||||
|
||||
debug!("📍 Pixel transformation: Δx={:.3}, Δy={:.3} -> final pixel=({:.1}, {:.1})",
|
||||
delta_x, delta_y, pixel_x, pixel_y);
|
||||
|
||||
// Check if pixel coordinates are within image bounds
|
||||
@ -2075,62 +2325,16 @@ impl StarChart {
|
||||
warn!("⚠️ Computed pixel ({:.1}, {:.1}) is outside image bounds [0, {}] x [0, {}]",
|
||||
pixel_x, pixel_y, naxis1, naxis2);
|
||||
} else {
|
||||
info!("✅ Pixel coordinates are within bounds");
|
||||
debug!("✅ Pixel coordinates are within bounds");
|
||||
}
|
||||
|
||||
let mut result_point = core::Point::new(pixel_x as i32, pixel_y as i32);
|
||||
|
||||
// Apply camera distortion correction if calibration is available
|
||||
if let Some(cal) = camera_cal {
|
||||
result_point = Self::apply_distortion_correction(result_point, cal)?;
|
||||
info!("🔧 Applied distortion correction: ({}, {}) -> ({}, {})",
|
||||
pixel_x as i32, pixel_y as i32, result_point.x, result_point.y);
|
||||
}
|
||||
|
||||
info!("🎯 FINAL RESULT: RA/Dec ({:.6}, {:.6}) -> pixel ({}, {})", ra, dec, result_point.x, result_point.y);
|
||||
info!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
debug!("🎯 FINAL RESULT: RA/Dec ({:.6}, {:.6}) -> pixel ({}, {})", ra, dec, result_point.x, result_point.y);
|
||||
debug!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
|
||||
Ok(result_point)
|
||||
}
|
||||
|
||||
/// Apply camera distortion correction using OpenCV
|
||||
fn apply_distortion_correction(point: core::Point, cal: &CameraCalibration) -> Result<core::Point> {
|
||||
// Create camera matrix
|
||||
let camera_data = [
|
||||
cal.camera_matrix[0], cal.camera_matrix[1], cal.camera_matrix[2],
|
||||
cal.camera_matrix[3], cal.camera_matrix[4], cal.camera_matrix[5],
|
||||
cal.camera_matrix[6], cal.camera_matrix[7], cal.camera_matrix[8]
|
||||
];
|
||||
let camera_matrix = core::Mat::from_slice_2d(&[
|
||||
&camera_data[0..3],
|
||||
&camera_data[3..6],
|
||||
&camera_data[6..9]
|
||||
])?;
|
||||
|
||||
// Create distortion coefficients vector
|
||||
let dist_coeffs = core::Mat::from_slice(&cal.distortion_coeffs)?;
|
||||
|
||||
// Convert point to undistorted coordinates
|
||||
let distorted_points = core::Mat::from_slice_2d(&[[point.x as f32, point.y as f32]])?;
|
||||
let mut undistorted_points = core::Mat::default();
|
||||
|
||||
calib3d::undistort_points(
|
||||
&distorted_points,
|
||||
&mut undistorted_points,
|
||||
&camera_matrix,
|
||||
&dist_coeffs,
|
||||
&core::Mat::default(), // No rectification
|
||||
&camera_matrix, // Use same camera matrix for output
|
||||
)?;
|
||||
|
||||
// Extract corrected coordinates
|
||||
let corrected_data: &[f32] = undistorted_points.data_typed()?;
|
||||
let corrected_x = corrected_data[0] as i32;
|
||||
let corrected_y = corrected_data[1] as i32;
|
||||
|
||||
Ok(core::Point::new(corrected_x, corrected_y))
|
||||
}
|
||||
|
||||
/// Draw NGC objects on the frame
|
||||
fn draw_ngc_objects(&self, state: &StarChartState, frame: &mut core::Mat) -> Result<()> {
|
||||
let ngc_color = core::Scalar::new(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user