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
|
// // Clean up temporary files if requested
|
||||||
// if !self.config.keep_temp_files {
|
if !self.config.keep_temp_files {
|
||||||
// self.cleanup_temp_files(base_name).await;
|
self.cleanup_temp_files(base_name).await;
|
||||||
// }
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,7 +150,7 @@ impl Default for SolverConfig {
|
|||||||
search_radius: None,
|
search_radius: None,
|
||||||
timeout_seconds: 60,
|
timeout_seconds: 60,
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
keep_temp_files: false,
|
keep_temp_files: true,
|
||||||
extra_args: Vec::new(),
|
extra_args: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -299,7 +299,7 @@ impl StarChart {
|
|||||||
.scale_range(options.index_scale_range.0, options.index_scale_range.1)
|
.scale_range(options.index_scale_range.0, options.index_scale_range.1)
|
||||||
.timeout(options.max_solve_time as u32)
|
.timeout(options.max_solve_time as u32)
|
||||||
.overwrite(true)
|
.overwrite(true)
|
||||||
.keep_temp_files(false);
|
.keep_temp_files(true);
|
||||||
|
|
||||||
// Add index files if specified
|
// Add index files if specified
|
||||||
if let Some(ref index_files) = options.index_file {
|
if let Some(ref index_files) = options.index_file {
|
||||||
@ -1473,164 +1473,6 @@ impl StarChart {
|
|||||||
Ok((center_ra, center_dec, radius, scale))
|
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
|
/// Create a temporary astrometry.net config file
|
||||||
fn create_config_file(working_dir: &Path, index_path: &str) -> Result<PathBuf> {
|
fn create_config_file(working_dir: &Path, index_path: &str) -> Result<PathBuf> {
|
||||||
let config_path = working_dir.join("astrometry.cfg");
|
let config_path = working_dir.join("astrometry.cfg");
|
||||||
@ -1645,84 +1487,6 @@ impl StarChart {
|
|||||||
Ok(config_path)
|
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
|
/// Apply star chart overlay to the given frame
|
||||||
pub async fn apply(&mut self, frame: &mut core::Mat, timestamp: DateTime<Utc>) -> Result<()> {
|
pub async fn apply(&mut self, frame: &mut core::Mat, timestamp: DateTime<Utc>) -> Result<()> {
|
||||||
if !self.options.enabled {
|
if !self.options.enabled {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user