Scientific and processing algorithms
The implemented processing chain from calibration frames to validated stacks and manual or automatic comet photometry.
Source-verified This chapter describes behaviour in the audited source baseline; representative observational data remain required for scientific validation.
Input normalization and compatibility
wstack_common.load_frame() exposes a common two-dimensional float32 image plus FITS header for supported FITS and DSLR RAW inputs. extract_metadata() normalizes frame type, shape, exposure, detector temperature, binning, instrument, filter, gain, offset and readout metadata.
Saturation is resolved by saturation_info(). Explicit detector/linearity metadata is distinguished from unreliable storage-range fallbacks; DATAMAX is not treated blindly as a detector clipping limit. Compatibility functions compare only known fields and return explicit mismatch reasons rather than silently coercing unlike data.
Scientific and Compact FITS writer
Scientific
The primary science array is written as float32 with BITPIX=-32, KOPRMODE=SCIENTIFIC, STORFMT=FLOAT32 and QSTEP=0. Non-finite invalid samples remain NaN.
Compact mapping
Finite values are mapped across codes -32767…32767; -32768 is reserved as BLANK. For non-constant data, QSTEP=(DATAMAX-DATAMIN)/65534, BSCALE=QSTEP and BZERO defines the physical zero point.
Safety estimator
Local noise is estimated robustly from horizontal and vertical neighbour differences using the median and MAD. The candidate is accepted only when QSTEP ≤ 0.25 × sigma_local. Otherwise the same already-computed floating-point product is written through the Scientific path. The policy is per product and never introduces clipping.
Policy evidence is written through REQMODE, QPOLICY, QMETHOD, QFRAC, QNOISE, QMAX, QNSAMP, QSAFE and QFALLBK. Actual storage is always described by KOPRMODE, STORFMT, BITPIX and QSTEP.
Reader normalization
Astropy scaling converts supported integer/float FITS to physical values. KOPR then creates an independent C-contiguous float32 array for the common numerical path. Auxiliary HDUs are identified and skipped when selecting the science image.
Master darks, bias/dark-flat and master flats
Dark-like masters
dark_build_masterdarks() classifies dark-like frames, groups compatible metadata, computes robust frame metrics, rejects invalid/saturated/outlying frames and combines eligible groups. Invalid master pixels are repaired through the dedicated dark-repair path and every included source is recorded in FITS history and QC reports.
Master flats
flat_build_masterflats() selects a compatible dark-flat or bias correction when available, subtracts that offset, masks clipped samples, normalizes every source flat, rejects whole-frame response/pattern outliers and combines the survivors. The final master is normalized to median one; non-positive or invalid response pixels are stored as invalid rather than converted into plausible gain values.
R75 correction guard and preflight boundary
The master-flat correction path is guarded before the build is committed. Stack Parameters performs one preflight pass before execution. These are orchestration/safety boundaries and do not replace the approved calibration or stacking mathematics.
Science-frame quality control
The QC layer estimates background and saturation fraction, detects stellar sources and measures FWHM/elongation. qc_assess_session() marks relative outliers within a compatible observing series using robust session statistics. Results are serializable through the processing manifest, cache and CSV report functions.
QC selection is operational metadata: it controls which calibrated/astrometric frames are enabled for later stages, but source files are not modified. The GUI persists Astro ON/OFF decisions in the session state.
LIGHT calibration
calib_calibrate_directory() scans or accepts explicit master files, finds unique compatible dark and flat products and applies the selected calibration chain to LIGHT frames. Master selection respects dimensions, binning, instrument/readout metadata, exposure/temperature tolerances and filter compatibility where required.
The output header records selected master filenames, calibration decisions and provenance. Invalid additive-master pixels are repaired through a dedicated safe path; invalid flat response cannot be divided into science data as if it were valid.
Astrometry input, backend and solution
R48 separates the blind online route from the constrained offline route. Online uploads use an empty constraint dictionary even when local position, scale, parity or downsampling values exist.
Offline automatic chain and explicit overrides
astro_build_astrometry_seed_plan() preserves these automatic priorities:
- Position: complete FITS WCS; supported FITS pointing; internal KOPR comet ephemeris only when FITS has no usable position.
- Scale: complete FITS WCS or FITS pixel-scale metadata; active KOPR camera/telescope geometry with FITS binning; standalone optical fallback only when integrated geometry is unavailable.
The dialog presents representative automatic values without converting them into persistent overrides. Only editing both RA/DEC fields creates a manual position override. Only editing both scale-limit fields creates a manual scale override; clearing both restores automatic scale. Per-frame automatic ephemerides remain recomputable at each exposure midpoint.
The host optics estimate is based on focal length, physical pixel size and FITS binning. The geometric mean of the X/Y plate scales is used and the local solve-field interval is ±15% around the estimate.
Blind online contract
The online payload contains normal authentication/licence fields and the FITS data, but no center_ra, center_dec, radius, scale_lower, scale_upper, scale_est, scale_err, scale_units, parity or downsample_factor. Job polling requests calibration only after status=success; queued/solving states produce heartbeats. A successful online centre/scale is trusted for the subsequent local solve.
Solver resolution
astro_resolve_offline_solver() prefers native solve-field. On native Windows only, when no native executable is found, it locates wsl.exe, builds an optional distribution prefix from KOPR_WSL_DISTRIBUTION, and probes command -v solve-field inside that distribution. The result is cached and represented as a backend dictionary containing mode, command prefix, executable and human-readable description.
astro__path_for_solver() translates host paths with wslpath -a -u. astro__build_solver_base_command() constructs a tokenized command so input, work, WCS, AXY, config and index paths remain safe when they contain spaces. No scientific seed or solver option changes between native and WSL modes.
WSL cancellation
astro__wrap_wsl_solver_command() launches the solver in a distinct Linux session when setsid is available, writes the session-leader PID to a host-visible file, then replaces the wrapper with the real solver through exec. astro__signal_wsl_solver() targets the process group (with a direct-PID fallback), using TERM followed by KILL after the bounded grace period.
Solved output is accepted only after celestial two-dimensional WCS validation, finite forward/inverse transforms and astrometric diagnostics. The optional online Astrometry.net path is blind; only a successful returned centre/scale becomes trusted input for the subsequent local result-validation contract.
Storage-preserving WCS write
The solved copy is opened without applying raw-pixel scaling, only relevant WCS/SIP/QC cards are replaced, and all extensions are preserved. A Compact input is not decoded and quantized a second time.
R45 bounded retry state machine
- source calibrated FITS with normal per-frame/adaptive seed;
- temporary robust solver image, original per-frame constraints and downsample 2;
- same robust image with broad radius/scale fallback.
An adaptive-seed failure returns to the independent frame seed at attempt 2. The orchestration must preserve the three-attempt ceiling and record attempt identity, solver-input mode and effective downsampling.
Conservative impulse cleaning
astro__clean_solver_image() classifies a pixel only when it is extreme against both global and local robust noise and lacks a neighbouring residual of comparable strength. Positive and negative isolated impulses are replaced with the 3×3 local median in a temporary FLOAT32 FITS under the solver work directory. The calibrated source is immutable and multi-pixel stellar profiles must survive.
Group-failure and index contracts
A required anchor that fails all attempts yields SKIPPED_ANCHOR_FAILED for remaining frames under the default stop policy. The independent-seed continuation is an explicit per-group override. Index discovery must accept 4100 and 4200 sibling families; missing 4100 is non-fatal, and automatic download remains 4206–4214.
R76 physical scale and down-leg light-time
The shared physical-scale resolver orders sources as: solved WCS or trusted online calibration; explicit manual override; FITS scale metadata; active camera/telescope optics using validated observation binning. The internal DE440s ephemeris path applies down-leg light-time. External Guide/Tycho/Horizons residuals are test diagnostics, not a universal acceptance threshold.
Relative-WCS quality control before stacking
Absolute astrometric success is not sufficient for stacking. stack__relative_wcs_diagnostics() detects registration stars, projects them between frames and evaluates unique nearest-neighbour matches. The production thresholds in this snapshot are:
| Criterion | Threshold |
|---|---|
| Minimum uniquely matched stars | 20 |
| Minimum match fraction | 0.20 |
| Maximum median residual | 0.70 px |
| Maximum 90th-percentile residual | 1.50 px |
| Nearest-neighbour search radius | 5 px |
The consensus reference's self-match is marked with WCSRREF and excluded from independent accepted-frame summary statistics. Stack headers and provenance retain accepted match-fraction/matched-star distributions and rejected-frame count. These diagnostics do not loosen acceptance thresholds.
Reprojection and stack combination
stack_prepare_group() first validates physical compatibility, then applies relative-WCS QC. It refuses to continue if all frames are rejected, if the remaining count is below the method minimum, or—without explicit user confirmation—if more than 25% of the requested series is lost to WCS rejection.
- Mean and sum require at least three accepted inputs.
- Sigma-clipped mean requires at least seven accepted inputs.
- Mean and sigma-clipped mean require matching exposure times; sum may retain differing exposures in the total-exposure metadata.
- Image shape, object, binning, instrument, filter, readout, gain and offset must be compatible when known.
- WCS pixel area may not differ by more than ±5%.
Each source is reprojected into the reference WCS in row chunks. Flux scaling uses the source/reference projected pixel-area relation so surface sampling changes do not silently change integrated signal. Geometric coverage is accumulated into NCONTRIB; accepted finite samples are accumulated into NVALID.
Sigma-clipped mean
The corrected policy performs exactly one symmetric median/MAD clipping pass using Astropy's sigma clip, with a default threshold of 5 sigma. The historical 20% rejection fraction is diagnostic only: rejected samples are never restored. Partial-coverage pixels remain NaN. NCONTRIB is pre-clip coverage and NVALID is post-clip accepted count.
Comet alignment
Star stacks use the common celestial WCS. Comet stacks apply an additional motion shift derived either from manual rate/position angle or KOPR's topocentric ephemeris positions. Paired star/comet creation shares input selection, reference geometry, pair identity and provenance.
Stack time and exposure contract
The effective epoch is the exposure-weighted mean of accepted input midpoints. DATE-BEG/DATE-END bound the accepted series; DATE-OBS, DATE-MID, DATE-AVG, MJD-OBS and MJD-AVG store that effective epoch. TELAPSE includes gaps, TIMEALGO=EXP-WMID, and TIMECOMP records timing completeness.
TOTEXP/XPOSURE sum accepted exposures. Mean and sigma-clipped mean use one-input EXPTIME and require equal positive exposure times; SUM uses total EXPTIME and can accept different positive exposure times.
Source-frame saturation provenance
A numeric peak in a mean or sum stack cannot establish whether every source exposure was linear. WStack therefore creates a binary saturation mask for each source frame, reprojects it with the science image and stores contribution counts:
NSATCON: saturated source contributions before clipping;NSATVAL: saturated contributions retained after combination.
SATCOMP states whether reliable limits were available for all, some or no source frames. WCCD maintains separate frozen saturation configurations for Comet and Stars. When provenance is complete and NSATVAL matches the science geometry, the map is authoritative and any positive aperture footprint rejects the measurement. The aggregate MEAN/SUM value is not compared with a per-frame ADC limit; an internal large finite limit is used only to disable that invalid secondary comparison.
Without a complete map, R47 resolves the numeric value by strict priority: explicit SATURATE/SATLEVEL/MAXADU/ADC-MAX; detector bit depth from ADCBITS/ADC-BITS/BITDEPTH/BITDEP/NBITS as 2**bits - 1; integer physical storage ceiling from BITPIX/BSCALE/BZERO; then a floating-type fallback of 1.0 for normalized 0–1 data or editable non-authoritative 65535. The finite data range is never used as the threshold itself.
Editing the GUI Saturation level creates a per-role manual_threshold override. This intentionally disables the complete map for that role and performs direct aperture-pixel comparison with the entered value. The state survives redraw, remeasurement and role switching, while replacement of one FITS resets only that role.
When wccd.py transfers explicit thresholds into autophot.py, the current Comet/Stars paths must exactly match the paths of the receiving job. This prevents a manual override from an earlier observation leaking into an unrelated pair. Diagnostics retain, per role, policy, user-visible threshold, threshold source, provenance status and whether the WStack map was used.
Manual WCCD and shared PA-adaptive ACF
photom.PhotomMatrix() builds an anti-aliased circular aperture weight matrix. Saturation and non-finite checks include fractional boundary pixels and consume the role-specific frozen saturation configuration. The identical contract routes manual stars, ordinary comet apertures and PA-adaptive ACF. koprfunc.BackgroundSky() provides the same-image local background.
Production comet filtering is implemented by acfcore.py as pa-adaptive-upper-v1. prepare_acf_context() freezes a local read-only float32 patch, masks, bounded centroid and local background. analyse_acf_context() can then evaluate parameter previews without repeating upstream work.
For each circular layer, the algorithm models the local brightness trend along position angle in a fixed 40° window. A dynamic robust-noise upper limit clips only positive outliers. The negative distribution is not clipped, so weak/negative samples are not lifted toward a positive floor.
The supported API is:
context = prepare_acf_context(...)
analysis = analyse_acf_context(context, parameters)
original = reset_acf_analysis(context)
record = acf_measurement_provenance(context, analysis)
line = append_acf_history(header, context, analysis)
photom.PhotometryACF() preserves its historical return tuple. photom.ACF_ALGORITHM_ID selects production behaviour; photom.LEGACY_ACF_ALGORITHM_ID is an explicit internal rollback selector.
m_inst = 40 - 2.5 log10(net_flux / EXPTIME). Local sky is used only for additive background subtraction. The observation preflight validates Comet/Stars EXPTIME with an absolute tolerance of 0.001 s and freezes the accepted value. TOTEXP is reporting metadata; no PHOTSCAL, KPRSCAL or user scale is introduced. The cumulative PA-adaptive ACF result is not overwritten by an ordinary aperture measurement.The comet centroid is bounded by the configured maximum shift from the clicked position and remains independent of the potentially large coma aperture.
Physical curve-of-growth model
The result plot and ICQ comment use the same model. For surface brightness I(ρ) ∝ ρ^γ, the cumulative magnitude model is:
m(<ρ) = m_ref + s log10(ρ / ρ_ref)
γ = -2 - s / 2.5
The preferred reference aperture is 10,000 km when it lies inside the measured interval; otherwise the geometric center of the interval is used. FitCurveOfGrowth() may select a continuous broken power law only when at least four apertures lie on each side and the broken fit improves BIC by more than six. Because cumulative apertures are correlated, the break remains a descriptive flag that must be checked against seeing, background, trails and boundaries.
The previous quadratic polynomial in log10(ρ) is not part of production output.
WCCD helper algorithms
Candidate-only comet finder
The paired model ranks candidates with ephemeris distance, significance, centroid stability, FWHM, elongation, stellar-PSF ratio, wing excess and trail diagnostics. Single-image mode omits the independent stellar PSF and is explicitly less reliable. The backend returns candidates and diagnostics; confirmation and centre commit belong to the GUI.
R59 uses a 120-arcsec search radius for the interactive WCCD finder while preserving the unattended 30-arcsec automatic-photometry gate and Primary/Extended classification. The GUI copy is renumbered after filtering/deduplication as C1..Cn. Image clicks near circles select rows; a click elsewhere creates an M1 starting position. A valid review object is created even for an empty numerical-candidate list. Pan/zoom modes suppress selection callbacks.
R60 centralizes comet centering in one production CometCentroid implementation in cometcentroid.py. koprfunc.py re-exports it and autophot.py calls the same bounded 2D Gaussian model. Manual WCCD clicks, automatic candidates and M1 therefore share one numerical definition. The confirmed centre is authoritative for WCCD state, the marker, Slice, aperture photometry and ACF.
Auto Star Measure backend
R55 preserves a single backend for ordinary and crowded fields. It retains catalogue routing, colour classes, basic WCS alignment, saturation provenance, coma masks, the FWHM acceptance interval 1.2–20 px, S/N sequence 50 → 40 → 30 → 20, iterative individual-zero-point clipping and the R50 star-count/scatter decisions.
- Projection guard: require finite in-image catalogue coordinates, a science-valid centre, a constructible minimum centroid patch and the configured valid fraction. Do not require validity of the entire later search box.
- Mask-aware centroid: use only finite science-valid samples; never zero-fill NaN. Validate whole-region coverage, near-complete core validity, local-background sample count, finite moments and each new iterated position. Emit the precise
centroid_*subreason. - Profile separation: use the search radius only for centroid location. Remeasure FWHM/elongation in a smaller adaptive profile from the compact component nearest the centroid. Prefer a local log-signal 2D Gaussian with constant background; fall back to masked compact moments.
- Robust global PSF: select quality, unsaturated, significant, shape-accepted and catalogue-isolated profiles. Use a robust median for compact distributions; use the lower 30% when a high-FWHM tail is present or isolation is sparse.
- Aperture: derive growth-curve radii, calibrator isolation and the 3.5× fallback from robust FWHM; cap fallback at 4.0×. The plateau still needs following stable increments.
- Post-aperture blend gate: recheck every candidate after radius selection with
max(original_blend_radius, aperture_radius + 0.75 × robust_fwhm). - Aperture NaN contract: any invalid sample with fractional aperture weight above tolerance yields
invalid_aperture; no flux interpolation or zero substitution is allowed. - Background: retain absolute/relative usable-pixel, quadrant, asymmetry, sector, gradient and coma checks. Mild geometric cropping requires stricter coverage and at least three representative quadrants. Only geometry/coverage failure may enter the checked
balanced_boxesfallback. Emit the precisebackground_*subreason. - R50 decisions retained: elongation ≤2.2 standard, 2.2–2.7
moderate_elongation, >2.7 reject; <3 final stars fail, 3–7 warn, ≥8 normal; >40% clipping warns; scatter ≤0.08 normal, 0.08–0.15 warning and >0.15 failure with diagnostics retained.
The backend returns a non-mutating result. The GUI commits atomically only after all hard gates pass. Diagnostics include raw/robust FWHM, robust method/source count, 25/50/75% widths, aperture/source, final blend gate, neighbours within the aperture, post-aperture blend rejects, background method, exact rejection subreasons, clipping statistics, zero point, scatter and result state.
Slice profile
An integer Bresenham line visits one physical image pixel per sample. Through-Comet geometry mirrors the selected half-line around the known centre and clips both sides symmetrically. Between-Points uses two explicit endpoints. No interpolation, strip averaging, background subtraction or calibration is performed.
Persistent ACF Review
The stored frozen ACF context is owned by the last successful ACF Comet measurement. GUI view changes are not invalidation events. Apply targets the Comet slot and uses source/context/run identity checks. Cancelled or failed replacement attempts leave the previous context intact.
R77–R78 canonical reference-star solver
Automatic and manual star measurements are normalized into one lossless canonical collection. The model preserves richer fields and separates the observer state user_enabled from the derived solver state pairwise_accepted.
Extinction is applied to q_i before selection. The solver performs a global optimization over the eligible stars instead of a greedy remove-largest-deviation loop. The admissibility rule is strict: for every accepted pair, abs(q_i - q_j) < 0.150 mag; equality at 0.150 mag fails.
The resulting accepted IDs and single common final solution are authoritative for Reference stars, Process obs. and Af-rho. No parallel post-success scatter gate is allowed to create a second answer, and internal pairwise audit text is not part of the ICQ comment contract.
R80 changes only the table projection: Use is checked when user_enabled AND quality_eligible AND pairwise_accepted is not False. Reason is the visible diagnostic and the redundant Pairwise status column is absent. The canonical record still retains observer and derived solver state separately.
R73 shared saturation and camera-validation contract
Shared image-domain saturation resolver
saturationpolicy.pyis the cross-module authority used by WCCD and Automatic Star Measure.- A complete source-frame map remains authoritative. Without it, the resolver obtains the established detector/manual base and converts it to the current image domain.
- The effective threshold must satisfy
effective_limit >= finite_image_maximum; the image maximum is never copied as the threshold. - Automatic and manual sources of the same base value must produce the same effective threshold.
SATCOMP=NONEand an unavailable source-peak provider are non-fatal when scalar resolution succeeds. Flat-top and bleeding morphology remain independent clipping vetoes.
The audited 240P FLOAT32 SUM fixture resolves base 65535 ADU, finite maximum 1036396.5625 ADU and multiplier 16 to an effective threshold of 1048560 ADU.
Camera decision core
- Evaluate camera policy before opening the selection dialog.
- Reconstruct native geometry from source dimensions and binning.
- Return
VERIFIED_EXACTfor an exact configured detector andVERIFIED_COMPATIBLEfor one unique supported active-area match. - Open the selection dialog only when no unambiguous verified result exists.