.. _algorithm_trace_detection: =========================== Trace Detection Algorithm =========================== Overview ======== Spectral trace detection identifies the spatial position of stellar/galactic spectra in 2D longslit images. pyKOSMOS++ uses cross-correlation with Gaussian templates to robustly detect traces even in low signal-to-noise conditions. Method: Cross-Correlation with Gaussian Templates ================================================== Algorithm Steps --------------- 1. **Collapse 2D spectrum spatially** (median along spectral direction) to create 1D spatial profile 2. **Create Gaussian template** with expected FWHM:: template[y] = exp(-0.5 * ((y - center) / sigma)^2) where sigma = FWHM / 2.355 3. **Cross-correlate** spatial profile with template using scipy.ndimage.correlate1d 4. **Detect peaks** in correlation signal using scipy.signal.find_peaks with: * Height threshold: min_snr * background_noise * Minimum separation: min_separation pixels 5. **Trace spatial position** along spectral direction: * For each detected peak position * Fit Gaussian centroid in overlapping spectral windows * Track center position vs wavelength Implementation -------------- .. code-block:: python from scipy.ndimage import correlate1d from scipy.signal import find_peaks def detect_traces_cross_correlation( data: np.ndarray, expected_fwhm: float = 4.0, min_snr: float = 3.0, min_separation: int = 20 ) -> List[Trace]: """ Detect spectral traces via cross-correlation. Parameters ---------- data : ndarray 2D calibrated spectrum (spatial x spectral) expected_fwhm : float Expected trace FWHM in spatial direction (pixels) min_snr : float Minimum signal-to-noise ratio for detection min_separation : int Minimum pixel separation between distinct traces Returns ------- traces : List[Trace] Detected trace objects with spatial positions """ # Collapse spatial profile spatial_profile = np.median(data, axis=1) # Create Gaussian template sigma = expected_fwhm / 2.355 y = np.arange(len(spatial_profile)) template = np.exp(-0.5 * ((y - len(y)//2) / sigma)**2) # Cross-correlate correlation = correlate1d(spatial_profile, template, mode='constant') # Detect peaks noise = np.std(correlation) height_threshold = min_snr * noise peaks, properties = find_peaks( correlation, height=height_threshold, distance=min_separation ) # Create Trace objects for each detection traces = [] for peak in peaks: trace = trace_spatial_position(data, peak, expected_fwhm) traces.append(trace) return traces Advantages ---------- **Robust to Noise:** Cross-correlation averages over multiple pixels, improving SNR compared to simple peak-finding. **Template Matching:** Using expected seeing profile improves detection accuracy for extended sources. **Multi-Trace Support:** Automatically detects multiple traces in multi-slit observations. Parameters ========== expected_fwhm ------------- **Type:** float **Default:** 4.0 pixels **Range:** 2.0 - 20.0 pixels Full-width half-maximum of expected trace profile. Should match observing conditions: * Good seeing (~0.8"): FWHM ≈ 3-4 pixels * Average seeing (~1.5"): FWHM ≈ 5-6 pixels * Poor seeing (~2.5"): FWHM ≈ 8-10 pixels * Extended galaxy: FWHM ≈ 10-20 pixels **How to determine:** .. code-block:: python # Measure from spatial profile import numpy as np spatial_profile = np.median(data, axis=1) # Find FWHM by fitting Gaussian from scipy.optimize import curve_fit def gaussian(x, amp, center, sigma): return amp * np.exp(-0.5 * ((x - center) / sigma)**2) popt, _ = curve_fit(gaussian, np.arange(len(spatial_profile)), spatial_profile) fwhm = 2.355 * popt[2] print(f"Measured FWHM: {fwhm:.2f} pixels") min_snr ------- **Type:** float **Default:** 3.0 **Range:** 1.0 - 10.0 Signal-to-noise threshold for detection. Lower values detect fainter traces but increase false positives. **Guidelines:** * **SNR ≥ 5:** Very reliable detections * **SNR ≥ 3:** Standard threshold, good balance * **SNR ≥ 2:** For faint extended sources (more false positives) * **SNR ≥ 10:** Very stringent, only brightest traces min_separation -------------- **Type:** int **Default:** 20 pixels **Range:** 5 - 100 pixels Minimum spatial separation between distinct traces. Prevents duplicate detections of the same trace. **Guidelines:** * Multi-slit: Set to ~0.8 × slit separation * Single object: Default (20 pixels) sufficient * Crowded field: Reduce to 10-15 pixels Trace Position Fitting ====================== After initial detection, trace positions are refined by fitting the spatial profile in spectral windows: .. code-block:: python def trace_spatial_position( data: np.ndarray, initial_position: int, window_width: int = 50 ) -> Trace: """Refine trace position along spectral direction.""" n_spatial, n_spectral = data.shape spatial_positions = [] spectral_pixels = [] # Slide window along spectral direction for i in range(0, n_spectral, window_width // 2): window = data[:, i:i+window_width] profile = np.median(window, axis=1) # Fit Gaussian centroid center = fit_gaussian_center(profile, initial_position) spatial_positions.append(center) spectral_pixels.append(i + window_width // 2) return Trace( trace_id=..., spatial_positions=np.array(spatial_positions), spectral_pixels=np.array(spectral_pixels) ) Performance =========== **Typical Performance:** * Detection time: 0.5-2 seconds for 2048×515 image * Memory usage: ~50 MB for full-resolution KOSMOS frame * False positive rate: <5% with default parameters **Failure Modes:** 1. **No traces detected:** * Spectrum too faint (SNR < 2) * Wrong expected_fwhm (off by factor of 2+) * Incorrect calibration (flat/bias issues) 2. **Multiple detections of same trace:** * min_separation too small * Trace has brightness variations 3. **Missed faint traces:** * min_snr too high * Need spatial binning to boost SNR References ========== * **pyKOSMOS** trace detection (Davenport et al. 2023) * **scipy.ndimage.correlate1d** documentation * **scipy.signal.find_peaks** for peak detection See Also ======== * :ref:`api_extraction` - API reference * :ref:`configuration` - Tuning detection parameters * :ref:`troubleshooting` - Common detection issues