From 97336407282fdd40b8a026d4219a98bd4d0595d9 Mon Sep 17 00:00:00 2001 From: grabbit Date: Sat, 28 Jun 2025 16:27:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=98=9F=E7=B3=BB?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E7=9A=84=E6=98=BE=E7=A4=BA=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=98=9F=E7=82=B9=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demos/star_chart_demo.rs | 5 +- src/overlay/star_chart.rs | 430 ++++++++++++++++++++++++++++---------- 2 files changed, 319 insertions(+), 116 deletions(-) diff --git a/demos/star_chart_demo.rs b/demos/star_chart_demo.rs index 35f4e09..e528113 100644 --- a/demos/star_chart_demo.rs +++ b/demos/star_chart_demo.rs @@ -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 diff --git a/src/overlay/star_chart.rs b/src/overlay/star_chart.rs index 4aa0df1..c22308f 100644 --- a/src/overlay/star_chart.rs +++ b/src/overlay/star_chart.rs @@ -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, +} + /// Constellation data loaded from files #[derive(Debug, Clone)] struct ConstellationData { @@ -213,11 +225,12 @@ struct NgcCatalogEntry { v_mag: Option, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] struct StarChartState { latest_hint: Option, stars: Vec, constellation_lines: Vec, + constellation_labels: Vec, ngc_objects: Vec, // Static catalog data (loaded once) constellation_catalog: Vec, @@ -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 = hdu.read_col(&mut fitsfile, "X") - .context("Failed to read 'X' column from .axy file")?; - let y_coords: Vec = 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 = 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, 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 = axy_hdu.read_col(&mut axy_fits, "X")?; + let y_coords: Vec = axy_hdu.read_col(&mut axy_fits, "Y")?; + let flux_vals: Vec = 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 = rdls_hdu.read_col(&mut rdls_fits, "RA")?; + let dec_coords: Vec = 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, axy_path: &Path) -> Result<()> { + let mut fitsfile = FitsFile::open(axy_path)?; + let hdu = fitsfile.hdu(1)?; + + let x_coords: Vec = hdu.read_col(&mut fitsfile, "X")?; + let y_coords: Vec = hdu.read_col(&mut fitsfile, "Y")?; + let flux_vals: Vec = 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::(&mut fits, "CRVAL1").unwrap_or(0.0); + let crval2 = hdu.read_key::(&mut fits, "CRVAL2").unwrap_or(0.0); + let crpix1 = hdu.read_key::(&mut fits, "CRPIX1").unwrap_or(0.0); + let crpix2 = hdu.read_key::(&mut fits, "CRPIX2").unwrap_or(0.0); + + let cd1_1 = hdu.read_key::(&mut fits, "CD1_1").unwrap_or(0.0); + let cd1_2 = hdu.read_key::(&mut fits, "CD1_2").unwrap_or(0.0); + let cd2_1 = hdu.read_key::(&mut fits, "CD2_1").unwrap_or(0.0); + let cd2_2 = hdu.read_key::(&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, 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 { + fn radec_to_pixel(ra: f64, dec: f64, wcs_path: &Path) -> Result { // 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::(&mut fits, "NAXIS1").unwrap_or(0); let naxis2 = hdu.read_key::(&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::(&mut fits, "PC1_2").unwrap_or(0.0); let pc2_1 = hdu.read_key::(&mut fits, "PC2_1").unwrap_or(0.0); let pc2_2 = hdu.read_key::(&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 { - // 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(