feat: 把solve-field抽象出来,因为linux和mac下参数行为不一致;
This commit is contained in:
parent
aea16be92e
commit
1254e5e829
@ -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)
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<SolutionHint>,
|
||||
_gps_status: &Arc<Mutex<GpsStatus>>
|
||||
) -> 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::<Vec<_>>()
|
||||
.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<PathBuf> {
|
||||
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::<f64>(&mut fits, "CRVAL1").unwrap_or(0.0);
|
||||
let dec = hdu.read_key::<f64>(&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::<f64>(&mut fits, "CD1_1").ok();
|
||||
let cd1_2 = hdu.read_key::<f64>(&mut fits, "CD1_2").ok();
|
||||
let cd2_1 = hdu.read_key::<f64>(&mut fits, "CD2_1").ok();
|
||||
let cd2_2 = hdu.read_key::<f64>(&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::<f64>(&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::<i64>(&mut fits, "IMAGEW")
|
||||
.or_else(|_| hdu.read_key::<i64>(&mut fits, "NAXIS1"))
|
||||
.unwrap_or(0);
|
||||
let image_height = hdu.read_key::<i64>(&mut fits, "IMAGEH")
|
||||
.or_else(|_| hdu.read_key::<i64>(&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<Utc>) -> Result<()> {
|
||||
if !self.options.enabled {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user