pub struct ImageAnalyzer { /* private fields */ }Expand description
Image analyzer with builder pattern.
Implementations§
Source§impl ImageAnalyzer
impl ImageAnalyzer
Sourcepub fn new() -> Self
pub fn new() -> Self
Examples found in repository?
3fn main() {
4 let dir = "tests/no_trails";
5 let mut files: Vec<_> = std::fs::read_dir(dir)
6 .unwrap()
7 .filter_map(|e| e.ok())
8 .filter(|e| e.path().extension().map_or(false, |ext| ext == "fits"))
9 .map(|e| e.path())
10 .collect();
11 files.sort();
12
13 println!(
14 "{:<12} {:>5} {:>5} {:>6} {:>6} {:>6} {:>6} {:>7} {:>8} {:>8} {:>6} {:>6} {:>6} {:>7}",
15 "FILE", "DET", "KEPT", "FWHM", "ECC", "SNR", "HFR", "SNR_W", "PSF_SIG", "FR_SNR", "W", "H", "R²", "TRAIL?"
16 );
17 println!("{}", "-".repeat(124));
18
19 for path in &files {
20 let name = path.file_name().unwrap().to_str().unwrap();
21 // Extract short name (frame number)
22 let short = if let Some(pos) = name.rfind('_') {
23 &name[pos + 1..name.len() - 5] // strip .fits
24 } else {
25 &name[..name.len().min(12)]
26 };
27
28 let analyzer = ImageAnalyzer::new();
29 match analyzer.analyze(path) {
30 Ok(r) => {
31 println!(
32 "{:<12} {:>5} {:>5} {:>6.2} {:>6.3} {:>6.1} {:>6.2} {:>7.3} {:>8.1} {:>8.1} {:>6} {:>6} {:>6.3} {:>7}",
33 short,
34 r.stars_detected,
35 r.stars.len(),
36 r.median_fwhm,
37 r.median_eccentricity,
38 r.median_snr,
39 r.median_hfr,
40 r.snr_weight,
41 r.psf_signal,
42 r.frame_snr,
43 r.width,
44 r.height,
45 r.trail_r_squared,
46 if r.possibly_trailed { "YES" } else { "no" },
47 );
48 }
49 Err(e) => {
50 println!("{:<12} ERROR: {}", short, e);
51 }
52 }
53 }
54}More examples
3fn analyze_file(label: &str, path: &str) {
4 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
5 println!("{label}");
6 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
7
8 let analyzer = ImageAnalyzer::new();
9 let r = analyzer.analyze(path).unwrap();
10
11 println!(" Stars detected: {}", r.stars_detected);
12 println!(" Stars after filter: {}", r.stars.len());
13 println!(" Median FWHM: {:.3}", r.median_fwhm);
14 println!(" Median eccentricity: {:.3}", r.median_eccentricity);
15 println!(" Median SNR: {:.1}", r.median_snr);
16 println!(" PSF signal: {:.1}", r.psf_signal);
17 println!(" Frame SNR: {:.1}", r.frame_snr);
18 println!(" Trail R²: {:.4}", r.trail_r_squared);
19 println!(" Possibly trailed: {}", r.possibly_trailed);
20
21 // Show theta distribution
22 let n = r.stars.len();
23 if n >= 5 {
24 let mut thetas: Vec<f32> = r.stars.iter().map(|s| s.theta.to_degrees()).collect();
25 thetas.sort_by(|a, b| a.total_cmp(b));
26
27 let (sum_cos, sum_sin) = r.stars.iter().fold((0.0f64, 0.0f64), |(sc, ss), s| {
28 let a = 2.0 * s.theta as f64;
29 (sc + a.cos(), ss + a.sin())
30 });
31 let mean_theta = (sum_sin.atan2(sum_cos) * 0.5).to_degrees();
32
33 println!("\n Theta ({} measured stars):", n);
34 println!(" Mean theta: {mean_theta:.1}deg");
35 println!(
36 " min={:.1} p25={:.1} median={:.1} p75={:.1} max={:.1}",
37 thetas[0],
38 thetas[n / 4],
39 thetas[n / 2],
40 thetas[3 * n / 4],
41 thetas[n - 1]
42 );
43 }
44
45 if !r.stars.is_empty() {
46 println!("\n Top 5 stars:");
47 for (i, s) in r.stars.iter().take(5).enumerate() {
48 println!(
49 " #{}: ecc={:.3} theta={:.1}deg fwhm={:.2} peak={:.0}",
50 i + 1,
51 s.eccentricity,
52 s.theta.to_degrees(),
53 s.fwhm,
54 s.peak
55 );
56 }
57 }
58 println!();
59}Sourcepub fn with_detection_sigma(self, sigma: f32) -> Self
pub fn with_detection_sigma(self, sigma: f32) -> Self
Star detection threshold in σ above background.
Sourcepub fn with_min_star_area(self, area: usize) -> Self
pub fn with_min_star_area(self, area: usize) -> Self
Reject connected components with fewer pixels than this (filters hot pixels).
Sourcepub fn with_max_star_area(self, area: usize) -> Self
pub fn with_max_star_area(self, area: usize) -> Self
Reject connected components with more pixels than this (filters galaxies/nebulae).
Sourcepub fn with_saturation_fraction(self, frac: f32) -> Self
pub fn with_saturation_fraction(self, frac: f32) -> Self
Reject stars with peak > fraction × 65535 (saturated).
Sourcepub fn with_max_stars(self, n: usize) -> Self
pub fn with_max_stars(self, n: usize) -> Self
Keep only the brightest N stars in the returned result.
Sourcepub fn without_debayer(self) -> Self
pub fn without_debayer(self) -> Self
Skip debayering for OSC images (less accurate but faster).
Sourcepub fn with_trail_threshold(self, threshold: f32) -> Self
pub fn with_trail_threshold(self, threshold: f32) -> Self
Set the R² threshold for trail detection. Images with Rayleigh R² above this are flagged as possibly trailed. Default: 0.5. Lower values are more aggressive (more false positives).
Sourcepub fn with_mrs_layers(self, layers: usize) -> Self
pub fn with_mrs_layers(self, layers: usize) -> Self
Set MRS wavelet noise layers. Uses à trous B3-spline wavelet to isolate noise from nebulosity/gradients. Default: 4.
Sourcepub fn with_measure_cap(self, n: usize) -> Self
pub fn with_measure_cap(self, n: usize) -> Self
Max stars to PSF-fit for statistics. Default 2000. Stars are sorted by flux (brightest first) before capping. Set to 0 to measure all detected stars (catalog export mode).
Sourcepub fn with_fit_max_iter(self, n: usize) -> Self
pub fn with_fit_max_iter(self, n: usize) -> Self
LM max iterations for pass-2 measurement fits. Default 25. Calibration pass always uses 50 iterations.
Sourcepub fn with_fit_tolerance(self, tol: f64) -> Self
pub fn with_fit_tolerance(self, tol: f64) -> Self
LM convergence tolerance for pass-2 measurement fits. Default 1e-4. Calibration pass always uses 1e-6.
Sourcepub fn with_fit_max_rejects(self, n: usize) -> Self
pub fn with_fit_max_rejects(self, n: usize) -> Self
Consecutive LM step rejects before early bailout. Default 5.
Sourcepub fn with_thread_pool(self, pool: Arc<ThreadPool>) -> Self
pub fn with_thread_pool(self, pool: Arc<ThreadPool>) -> Self
Use a custom rayon thread pool.
Sourcepub fn analyze<P: AsRef<Path>>(&self, path: P) -> Result<AnalysisResult>
pub fn analyze<P: AsRef<Path>>(&self, path: P) -> Result<AnalysisResult>
Analyze a FITS or XISF image file.
Examples found in repository?
3fn main() {
4 let dir = "tests/no_trails";
5 let mut files: Vec<_> = std::fs::read_dir(dir)
6 .unwrap()
7 .filter_map(|e| e.ok())
8 .filter(|e| e.path().extension().map_or(false, |ext| ext == "fits"))
9 .map(|e| e.path())
10 .collect();
11 files.sort();
12
13 println!(
14 "{:<12} {:>5} {:>5} {:>6} {:>6} {:>6} {:>6} {:>7} {:>8} {:>8} {:>6} {:>6} {:>6} {:>7}",
15 "FILE", "DET", "KEPT", "FWHM", "ECC", "SNR", "HFR", "SNR_W", "PSF_SIG", "FR_SNR", "W", "H", "R²", "TRAIL?"
16 );
17 println!("{}", "-".repeat(124));
18
19 for path in &files {
20 let name = path.file_name().unwrap().to_str().unwrap();
21 // Extract short name (frame number)
22 let short = if let Some(pos) = name.rfind('_') {
23 &name[pos + 1..name.len() - 5] // strip .fits
24 } else {
25 &name[..name.len().min(12)]
26 };
27
28 let analyzer = ImageAnalyzer::new();
29 match analyzer.analyze(path) {
30 Ok(r) => {
31 println!(
32 "{:<12} {:>5} {:>5} {:>6.2} {:>6.3} {:>6.1} {:>6.2} {:>7.3} {:>8.1} {:>8.1} {:>6} {:>6} {:>6.3} {:>7}",
33 short,
34 r.stars_detected,
35 r.stars.len(),
36 r.median_fwhm,
37 r.median_eccentricity,
38 r.median_snr,
39 r.median_hfr,
40 r.snr_weight,
41 r.psf_signal,
42 r.frame_snr,
43 r.width,
44 r.height,
45 r.trail_r_squared,
46 if r.possibly_trailed { "YES" } else { "no" },
47 );
48 }
49 Err(e) => {
50 println!("{:<12} ERROR: {}", short, e);
51 }
52 }
53 }
54}More examples
3fn analyze_file(label: &str, path: &str) {
4 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
5 println!("{label}");
6 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
7
8 let analyzer = ImageAnalyzer::new();
9 let r = analyzer.analyze(path).unwrap();
10
11 println!(" Stars detected: {}", r.stars_detected);
12 println!(" Stars after filter: {}", r.stars.len());
13 println!(" Median FWHM: {:.3}", r.median_fwhm);
14 println!(" Median eccentricity: {:.3}", r.median_eccentricity);
15 println!(" Median SNR: {:.1}", r.median_snr);
16 println!(" PSF signal: {:.1}", r.psf_signal);
17 println!(" Frame SNR: {:.1}", r.frame_snr);
18 println!(" Trail R²: {:.4}", r.trail_r_squared);
19 println!(" Possibly trailed: {}", r.possibly_trailed);
20
21 // Show theta distribution
22 let n = r.stars.len();
23 if n >= 5 {
24 let mut thetas: Vec<f32> = r.stars.iter().map(|s| s.theta.to_degrees()).collect();
25 thetas.sort_by(|a, b| a.total_cmp(b));
26
27 let (sum_cos, sum_sin) = r.stars.iter().fold((0.0f64, 0.0f64), |(sc, ss), s| {
28 let a = 2.0 * s.theta as f64;
29 (sc + a.cos(), ss + a.sin())
30 });
31 let mean_theta = (sum_sin.atan2(sum_cos) * 0.5).to_degrees();
32
33 println!("\n Theta ({} measured stars):", n);
34 println!(" Mean theta: {mean_theta:.1}deg");
35 println!(
36 " min={:.1} p25={:.1} median={:.1} p75={:.1} max={:.1}",
37 thetas[0],
38 thetas[n / 4],
39 thetas[n / 2],
40 thetas[3 * n / 4],
41 thetas[n - 1]
42 );
43 }
44
45 if !r.stars.is_empty() {
46 println!("\n Top 5 stars:");
47 for (i, s) in r.stars.iter().take(5).enumerate() {
48 println!(
49 " #{}: ecc={:.3} theta={:.1}deg fwhm={:.2} peak={:.0}",
50 i + 1,
51 s.eccentricity,
52 s.theta.to_degrees(),
53 s.fwhm,
54 s.peak
55 );
56 }
57 }
58 println!();
59}Sourcepub fn analyze_data(
&self,
data: &[f32],
width: usize,
height: usize,
channels: usize,
) -> Result<AnalysisResult>
pub fn analyze_data( &self, data: &[f32], width: usize, height: usize, channels: usize, ) -> Result<AnalysisResult>
Analyze pre-loaded f32 pixel data.
data: planar f32 pixel data (for 3-channel: RRRGGGBBB layout).
width: image width.
height: image height.
channels: 1 for mono, 3 for RGB.
Sourcepub fn analyze_raw(
&self,
meta: &ImageMetadata,
pixels: &PixelData,
) -> Result<AnalysisResult>
pub fn analyze_raw( &self, meta: &ImageMetadata, pixels: &PixelData, ) -> Result<AnalysisResult>
Analyze pre-read raw pixel data (skips file I/O).
Accepts ImageMetadata and borrows PixelData, handling u16→f32
conversion and green-channel interpolation for OSC images internally.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for ImageAnalyzer
impl !RefUnwindSafe for ImageAnalyzer
impl Send for ImageAnalyzer
impl Sync for ImageAnalyzer
impl Unpin for ImageAnalyzer
impl UnsafeUnpin for ImageAnalyzer
impl !UnwindSafe for ImageAnalyzer
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more