From 1254e5e82969099409e5fa4d55094ef1c98d6d93 Mon Sep 17 00:00:00 2001 From: grabbit Date: Sun, 29 Jun 2025 11:57:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8A=8Asolve-field=E6=8A=BD=E8=B1=A1?= =?UTF-8?q?=E5=87=BA=E6=9D=A5=EF=BC=8C=E5=9B=A0=E4=B8=BAlinux=E5=92=8Cmac?= =?UTF-8?q?=E4=B8=8B=E5=8F=82=E6=95=B0=E8=A1=8C=E4=B8=BA=E4=B8=8D=E4=B8=80?= =?UTF-8?q?=E8=87=B4=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/astrometry/solver.rs | 6 +- src/astrometry/types.rs | 2 +- src/overlay/star_chart.rs | 238 +------------------------------------- 3 files changed, 5 insertions(+), 241 deletions(-) diff --git a/src/astrometry/solver.rs b/src/astrometry/solver.rs index fccfc1f..20cdcdf 100644 --- a/src/astrometry/solver.rs +++ b/src/astrometry/solver.rs @@ -224,9 +224,9 @@ impl AstrometrySolver { } // // Clean up temporary files if requested - // if !self.config.keep_temp_files { - // self.cleanup_temp_files(base_name).await; - // } + if !self.config.keep_temp_files { + self.cleanup_temp_files(base_name).await; + } Ok(result) } diff --git a/src/astrometry/types.rs b/src/astrometry/types.rs index 4654128..2bde521 100644 --- a/src/astrometry/types.rs +++ b/src/astrometry/types.rs @@ -150,7 +150,7 @@ impl Default for SolverConfig { search_radius: None, timeout_seconds: 60, overwrite: true, - keep_temp_files: false, + keep_temp_files: true, extra_args: Vec::new(), } } diff --git a/src/overlay/star_chart.rs b/src/overlay/star_chart.rs index 9ec1164..67e1d58 100644 --- a/src/overlay/star_chart.rs +++ b/src/overlay/star_chart.rs @@ -299,7 +299,7 @@ impl StarChart { .scale_range(options.index_scale_range.0, options.index_scale_range.1) .timeout(options.max_solve_time as u32) .overwrite(true) - .keep_temp_files(false); + .keep_temp_files(true); // Add index files if specified if let Some(ref index_files) = options.index_file { @@ -1473,164 +1473,6 @@ impl StarChart { Ok((center_ra, center_dec, radius, scale)) } - /// Legacy run_astrometry method - kept for compatibility but marked as deprecated - #[deprecated(note = "Use run_astrometry_with_solver instead")] - async fn run_astrometry( - _frame: &Frame, - working_dir: &Path, - options: &StarChartOptions, - previous_hint: &Option, - _gps_status: &Arc> - ) -> Result<(SolutionHint, PathBuf, PathBuf)> { - // Build the solve-field command with appropriate parameters - let mut command = Command::new(&options.solve_field_path); - - // Input file - let input_file = working_dir.join("current_frame.jpg"); - command.arg(&input_file); - - // Common options - command.arg("--no-plot"); // Don't create plots - command.arg("--overwrite"); // Overwrite existing files - command.arg("--dir"); // Output directory - command.arg(working_dir); - command.arg("--cpulimit"); - command.arg("60"); - - // Scale options and hints - command.arg("--scale-units").arg("arcsecperpix"); - - // Use previous hint if available - if let Some(hint) = previous_hint { - debug!("Using previous solution as hint: RA={}, Dec={}, Scale={}", - hint.ra, hint.dec, hint.scale); - - // Provide RA/Dec center hint - command.arg("--ra").arg(hint.ra.to_string()); - command.arg("--dec").arg(hint.dec.to_string()); - - // Provide search radius hint (with some margin) - command.arg("--radius").arg((hint.radius * 1.2).to_string()); - - // Provide scale hint (with 10% margin) - let scale_low = hint.scale * 0.9; - let scale_high = hint.scale * 1.1; - command.arg("--scale-low").arg(scale_low.to_string()); - command.arg("--scale-high").arg(scale_high.to_string()); - } else { - 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 - // Don't provide RA/Dec hints for the first solve to allow full-sky search - } - - // Index path - if !options.index_path.is_empty() { - command.arg("--config").arg(Self::create_config_file(working_dir, &options.index_path)?); - } - - // Use index files if specified - if let Some(index_files) = &options.index_file { - for index_file in index_files { - if std::path::Path::new(index_file).exists() { - info!("Using index file: {}", index_file); - command.arg("--index-file").arg(index_file); - } else { - warn!("Index file not found: {}", index_file); - } - } - } - - // Additional options for better handling of large images - command.arg("--depth").arg("10,20,30,40,50"); // Try different numbers of stars - command.arg("--downsample").arg("2"); // Downsample large images by factor of 2 - command.arg("--no-remove-lines"); // Don't remove line patterns (might be meteor trails) - command.arg("--crpix-center"); - command.arg("--no-plots"); // Don't create plot files to save time - // command.arg("--verbose"); // Enable verbose output for debugging - - // Execute command with timeout - // debug!("Running astrometry.net solve-field command: {:?}", command); - - // Print the full command line for debugging - let full_command = format!("{} {}", - &options.solve_field_path, - command.get_args() - .map(|arg| format!("\"{}\"", arg.to_string_lossy())) - .collect::>() - .join(" ") - ); - // debug!("Full solve-field command: {}", full_command); - - // Convert command to tokio::process::Command - let mut tokio_command = tokio::process::Command::new(&options.solve_field_path); - tokio_command.args(command.get_args()); - - // Set timeout for the command - let timeout = Duration::from_secs(options.max_solve_time); - let result = tokio::time::timeout(timeout, tokio_command.output()).await; - - match result { - Ok(output_result) => { - match output_result { - Ok(output) => { - 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); - - // The real test for success is if the WCS file was created. - let wcs_file = input_file.with_extension("wcs"); - let axy_file = input_file.with_extension("axy"); - if wcs_file.exists() { - // info!("WCS file found, solve succeeded."); - let solution = Self::parse_wcs_solution_new(&wcs_file)?; - let hint = SolutionHint { - ra: solution.0, - dec: solution.1, - radius: solution.2, - scale: solution.3, - timestamp: Utc::now(), - }; - return Ok((hint, wcs_file, axy_file)); - } else { - error!("solve-field did not produce a WCS file."); - error!("Expected WCS file at: {:?}", wcs_file); - error!("Command exit code: {}", output.status); - error!("Working directory contents:"); - if let Ok(entries) = std::fs::read_dir(working_dir) { - for entry in entries { - if let Ok(entry) = entry { - error!(" - {:?}", entry.path()); - } - } - } - return Err(anyhow::anyhow!( - "WCS file not found at {:?}. Command exit code: {}. stdout: {}, stderr: {}", - wcs_file, - output.status, - stdout_str, - stderr_str - )); - } - } - Err(e) => { - return Err(anyhow::anyhow!("Failed to execute solve-field: {}", e)); - } - } - } - Err(_) => { - // Kill the process if it times out - // Note: In a real implementation, we'd need to find and kill the process - error!("solve-field command timed out after {} seconds. Consider checking for and manually terminating any lingering 'solve-field' processes.", options.max_solve_time); - // TODO: Implement proper process killing on timeout - return Err(anyhow::anyhow!("solve-field timed out after {} seconds", options.max_solve_time)); - } - } - } - /// Create a temporary astrometry.net config file fn create_config_file(working_dir: &Path, index_path: &str) -> Result { let config_path = working_dir.join("astrometry.cfg"); @@ -1645,84 +1487,6 @@ impl StarChart { Ok(config_path) } - /// Parse a WCS solution file to extract key parameters - fn parse_wcs_solution(wcs_file: &Path) -> Result<(f64, f64, f64, f64)> { - info!("🔍 PARSING WCS SOLUTION from file: {:?}", wcs_file); - - let mut fits = FitsFile::open(wcs_file) - .context(format!("Failed to open WCS FITS file at {:?}", wcs_file))?; - let hdu = fits.primary_hdu()?; - - let ra = hdu.read_key::(&mut fits, "CRVAL1").unwrap_or(0.0); - let dec = hdu.read_key::(&mut fits, "CRVAL2").unwrap_or(0.0); - - info!("🌍 Field center coordinates: RA={:.6}°, Dec={:.6}°", ra, dec); - - // Try to calculate scale from CD matrix first, as it's more accurate - let cd1_1 = hdu.read_key::(&mut fits, "CD1_1").ok(); - let cd1_2 = hdu.read_key::(&mut fits, "CD1_2").ok(); - let cd2_1 = hdu.read_key::(&mut fits, "CD2_1").ok(); - let cd2_2 = hdu.read_key::(&mut fits, "CD2_2").ok(); - - let mut scale = 0.0; - if let (Some(cd1_1), Some(cd1_2), Some(cd2_1), Some(cd2_2)) = (cd1_1, cd1_2, cd2_1, cd2_2) { - // Pixel scale in degrees is sqrt of the determinant of the CD matrix - let determinant = (cd1_1 * cd2_2 - cd1_2 * cd2_1).abs(); - scale = determinant.sqrt() * 3600.0; // Convert degrees to arcseconds - info!("📐 CD matrix scale calculation: det={:.8e}, scale={:.3} arcsec/px", determinant, scale); - info!("📐 CD matrix: [{:.8}, {:.8}] [{:.8}, {:.8}]", cd1_1, cd1_2, cd2_1, cd2_2); - } else if let Ok(cdelt1) = hdu.read_key::(&mut fits, "CDELT1") { - // Fallback to CDELT if CD matrix is not present - scale = cdelt1.abs() * 3600.0; - info!("📐 CDELT scale calculation: CDELT1={:.8}, scale={:.3} arcsec/px", cdelt1, scale); - } else { - warn!("⚠️ No CD matrix or CDELT found in WCS file"); - } - - // Calculate radius from image dimensions and pixel scale. - // Try IMAGEW/IMAGEH first (astrometry.net format), then fallback to NAXIS1/NAXIS2 - let image_width = hdu.read_key::(&mut fits, "IMAGEW") - .or_else(|_| hdu.read_key::(&mut fits, "NAXIS1")) - .unwrap_or(0); - let image_height = hdu.read_key::(&mut fits, "IMAGEH") - .or_else(|_| hdu.read_key::(&mut fits, "NAXIS2")) - .unwrap_or(0); - - info!("📏 Image dimensions: width={}, height={}", image_width, image_height); - - let radius = if scale > 0.0 && image_width > 0 && image_height > 0 { - // Approximate field of view radius (diagonal / 2 * pixel_scale) - let diagonal_pixels = ((image_width as f64).powi(2) + (image_height as f64).powi(2)).sqrt(); - let diagonal_arcsec = diagonal_pixels * scale; - let radius_deg = diagonal_arcsec / 2.0 / 3600.0; - info!("📐 Field radius calculation: diagonal={:.1}px, diagonal_arcsec={:.1}, radius={:.3}°", - diagonal_pixels, diagonal_arcsec, radius_deg); - radius_deg - } else { - // If we can't determine image size, use a conservative default - warn!("⚠️ Cannot calculate field radius - using default 10.0°"); - if image_width == 0 && image_height == 0 { - warn!("⚠️ Both NAXIS1/NAXIS2 and IMAGEW/IMAGEH are 0!"); - } - 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}°", - ra, dec, scale, radius); - - if ra == 0.0 && dec == 0.0 { - warn!("⚠️ CRVAL1 and CRVAL2 are both zero - this may indicate parsing failure"); - return Err(anyhow::anyhow!("Failed to parse RA/Dec from WCS file - both coordinates are zero")); - } - - if scale == 0.0 { - warn!("⚠️ Could not determine pixel scale from WCS file"); - } - - info!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - Ok((ra, dec, radius, scale)) - } - /// Apply star chart overlay to the given frame pub async fn apply(&mut self, frame: &mut core::Mat, timestamp: DateTime) -> Result<()> { if !self.options.enabled {