Pipeline Overview — AQMH (Default) vs. Classic Tile Compile¶
C++ Implementierung:
runner_pipeline.cpp,runner_phase_local_metrics.cppDefault method:aqmh(Adaptive Quality Map Harvesting) Alternative method:classic_tile_compile
Übersicht¶
tile_compile unterstützt zwei Rekonstruktionsmethoden, die über method in der Konfiguration gewählt werden:
| Method | Beschreibung | Standard |
|---|---|---|
aqmh |
Adaptive Quality Map Harvesting — pixelweise Qualitätsbewertung mit Pyramidendarstellung, Cherry-Pick-Frame-Selektion, unabhängige Rekonstruktion | ✅ Default |
classic_tile_compile |
Klassische tile-basierte Rekonstruktion mit lokalen Tile-Metriken, Clustering und synthetischen Frames | Optional |
// config.cpp: normalizeMethod()
if (config.method.empty()) config.method = "aqmh";
if (config.method == "aqmh") config.aqmh.enabled = true;
else if (config.method == "classic_tile_compile") config.aqmh.enabled = false;
Pipeline-Phasen¶
Die Core-Phasen 0–18 bleiben methodenunabhängig. AQMH verwendet für die
Rekonstruktion zusätzlich die eigenen Phasen 19–22; diese sind keine bloße
Umbenennung von LOCAL_METRICS und TILE_RECONSTRUCTION.
| ID | Enum | AQMH-Verhalten | Classic-Verhalten |
|---|---|---|---|
| 0 | SCAN_INPUT |
Identisch | Identisch |
| 1 | REGISTRATION |
Identisch | Identisch |
| 2 | PREWARP |
Identisch | Identisch |
| 3 | CHANNEL_SPLIT |
Identisch (Metadaten) | Identisch (Metadaten) |
| 4 | NORMALIZATION |
Identisch | Identisch |
| 5 | GLOBAL_METRICS |
Identisch | Identisch |
| 6 | TILE_GRID |
Identisch | Identisch |
| 7 | COMMON_OVERLAP |
Identisch | Identisch |
| 8 | LOCAL_METRICS |
Nicht emittiert; AQMH verwendet AQMH_MAPS (Phase 19) |
Tile-Metriken pro (Frame, Tile) |
| 9 | TILE_RECONSTRUCTION |
Nicht emittiert; AQMH verwendet AQMH_RECONSTRUCTION (Phase 21) |
Tile-basierte Rekonstruktion mit W_f,t = G_f × L_f,t, OLA |
| 10 | STATE_CLUSTERING |
Nicht emittiert | Aktiv, sofern nicht Reduced Mode |
| 11 | SYNTHETIC_FRAMES |
skipped, Grund aqmh_independent_reconstruction |
Aktiv, sofern nicht Reduced Mode |
| 12 | STACKING |
AQMH-Pass-through des Rekonstruktionsergebnisses | Sigma-Clip-/Mean-Stacking der synthetischen Frames |
| 13 | DEBAYER |
Identisch | Identisch |
| 14 | ASTROMETRY |
Identisch (optional) | Identisch (optional) |
| 15 | BGE |
Identisch (optional) | Identisch (optional) |
| 16 | PCC |
Identisch (optional) | Identisch (optional) |
| 17 | HYPERMETRIC_STRETCH |
Identisch (optional) | Identisch (optional) |
| 18 | DONE |
Identisch | Identisch |
| 19 | AQMH_MAPS | Pyramid-Quality-Maps und Cache | Nicht verwendet |
| 20 | AQMH_GLOBAL_QUALITY | Globale AQMH-Frame-Gewichte | Nicht verwendet |
| 21 | AQMH_RECONSTRUCTION | Pixelweise AQMH-Rekonstruktion | Nicht verwendet |
| 22 | AQMH_DIAGNOSTICS | Karten-/Qualitätsdiagnostik | Nicht verwendet |
Validation ist ein Qualitätsblock zwischen
STACKINGundDEBAYER, aber keine eigene Phase.
AQMH-spezifische Phasen im Detail¶
Phase 19: AQMH_MAPS (AQMH-Erweiterungsphase)¶
Wenn aqmh.enabled = true:
- Classic Tile-Metriken werden nicht berechnet (
compute_classic_local_metrics = false) - Stattdessen werden pyramid-basierte Qualitätskarten pro Frame berechnet:
- Multi-Scale-Sharpness und SNR über Pyramid-Level
- Artefakt-Detektion (
k_artifact,frac_artifact_max) - Qualitätskarte
Q_mappro Frame wird inQualityMapCachegespeichert (runs/<id>/cache/aqmh/) - Phase-Anzeige:
AQMH_MAPS(Enum 19) - Artifact:
aqmh_metrics.json(stattlocal_metrics.json) - CUDA verwendet pro Worker einen eigenen Stream und gecachte
cudafilters-Boxfilter; OpenCL nutztUMat. Bei fehlendem Backend erfolgt derselbe CPU-Pfad.
// runner_phase_local_metrics.cpp
const bool compute_classic_local_metrics = !cfg.aqmh.enabled;
const std::string phase_display_name =
compute_classic_local_metrics ? "LOCAL_METRICS" : "AQMH_MAPS";
AQMH-Konfiguration¶
| Parameter | Beschreibung | Default |
|---|---|---|
aqmh.pyramid.scales |
Anzahl Pyramid-Level (1–8) | 4 |
aqmh.pyramid.base_window_px |
Basis-Fenstergröße | 4 |
aqmh.pyramid.w_sharp |
Gewicht Sharpness | 0.6 |
aqmh.pyramid.w_snr |
Gewicht SNR | 0.4 |
aqmh.pyramid.k_artifact |
Artefakt-Schwellwert | 3.0 |
aqmh.pyramid.frac_artifact_max |
Max. Artefakt-Anteil | 0.25 |
aqmh.storage.resolution_divisor |
Speicher-Auflösungsteiler (1/2/4) | 2 |
aqmh.storage.dtype |
Speichertyp (float32/uint16/uint8) | uint16 |
aqmh.storage.max_resident_maps |
Max. im RAM gehaltene Karten (0–16) | 2 |
aqmh.cherry_pick.enabled |
Cherry-Pick-Frame-Selektion | false |
aqmh.cherry_pick.k_min_required |
Run-Gate und Min. Samples pro Pixel | 20 |
aqmh.cherry_pick.k_frac |
Fraktion der besten Frames | 0.30 |
Phase 21: AQMH-Rekonstruktion (AQMH-Erweiterungsphase)¶
Wenn aqmh.enabled = true:
- Pixelweise Rekonstruktion statt tile-basiert:
- Für jeden Pixel: Quality-Map-Werte aller Frames werden mit globalen Gewichten
G_fkombiniert - Sigma-Clip-Rejection auf Pixel-Ebene
- Optional Cherry-Pick: nur die
kbesten Frames pro Pixel werden verwendet - Kein Clustering, keine synthetischen Frames
- Artifact:
aqmh_reconstruction.jsonundoutputs/aqmh_reconstructed_raw.fit - Die globale Gewichtung wird anschließend als eigene Phase
AQMH_GLOBAL_QUALITY(Enum 20) protokolliert. - Ohne Cherry-Pick läuft die zweipassige Welford-/Sigma-Clip-Rekonstruktion bei
opencv_cudastreaming auf der GPU. Akkumulatoren bleiben resident; jeweils nur ein Frame und eine Quality-Map werden übertragen. Cherry-Pick und CUDA-Fehler fallen kontrolliert auf CPU zurück.
// runner_pipeline.cpp
if (cfg.aqmh.enabled) {
auto aqmh_recon = aqmh_reconstruction_ops.reconstruct_aqmh(
frames.size(), aqmh_frame_loader, aqmh_cache.get(),
global_weights, common_valid_mask,
canvas_width, canvas_height, aqmh_recon_cfg,
aqmh_reconstruction_stream.get(0));
recon = aqmh_recon.output;
}
Phase 10–11: STATE_CLUSTERING und SYNTHETIC_FRAMES (skipped)¶
Bei AQMH werden beide Phasen übersprungen:
const bool skip_clustering_for_aqmh = cfg.aqmh.enabled;
// -> phase_end(STATE_CLUSTERING, "skipped", {"reason": "aqmh_independent_reconstruction"})
// -> phase_end(SYNTHETIC_FRAMES, "skipped", {"reason": "aqmh_independent_reconstruction"})
Pipeline-Flow-Diagramm (AQMH, Default)¶
┌─────────────────────────────────────────────────────────────┐
│ INPUT: MONO / OSC RAW FITS FRAMES │
└────────────────────────────┬────────────────────────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 0: SCAN_INPUT │
│ • FITS dimensions + header │
│ • Color mode (MONO/OSC) │
│ • Bayer pattern detection │
│ • Linearity validation │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 1: REGISTRATION │
│ • Cascaded fallbacks │
│ • CC / warp metrics │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 2: PREWARP │
│ • Full-frame canvas warp │
│ • CFA-safe (OSC) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 3: CHANNEL_SPLIT │
│ (metadata-only) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 4: NORMALIZATION │
│ • Sigma-clip BG mask │
│ • Additive background B_f │
│ • Photometric scale P_f │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 5: GLOBAL_METRICS │
│ • B_f, σ_f, E_f per frame │
│ • G_f = exp(α·B̃+β·σ̃+γ·Ẽ) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 6: TILE_GRID │
│ • FWHM probe (central ROI) │
│ • Uniform tile grid │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 7: COMMON_OVERLAP │
│ • Pixelwise valid overlap │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 19: AQMH_MAPS │
│ • Pyramid quality maps │
│ • Multi-scale sharpness+SNR │
│ • QualityMapCache (disk) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 21: AQMH_RECONSTRUCTION│
│ • Pixel-wise AQMH weighted │
│ • Sigma-clip rejection │
│ • Cherry-pick (optional) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 10: STATE_CLUSTERING │
│ ⏭ SKIPPED (aqmh) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 11: SYNTHETIC_FRAMES │
│ ⏭ SKIPPED (aqmh) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 12: STACKING │
│ • Pass-through (AQMH) │
│ • Validation block │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PHASE 13: DEBAYER │
│ • OSC: NN demosaic -> RGB │
│ • MONO: pass-through │
└──────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ PHASE 14: ASTROMETRY │
│ • ASTAP solve / WCS │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ PHASE 15: BGE │
│ • Optional gradient removal│
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ PHASE 16: PCC │
│ • Photometric color cal. │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ PHASE 17: HMS │
│ • HyperMetric Stretch │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ PHASE 18: DONE │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ OUTPUTS: │
│ • stacked.fits │
│ • reconstructed_L.fit │
│ • stacked_rgb.fits (OSC) │
│ • stacked_rgb_solve.fits │
│ • stacked_rgb_bge.fits │
│ • stacked_rgb_pcc.fits │
│ • stacked_rgb_hms.fits │
│ • aqmh_metrics.json │
│ • 12 artifact JSON files │
│ • run_events.jsonl │
└─────────────────────────────┘
Classic Tile Compile Pipeline (method: classic_tile_compile)¶
Bei method: classic_tile_compile gelten die Phasen 8–11 wie ursprünglich:
- Phase 8: LOCAL_METRICS — Tile-Metriken (FWHM, Roundness, Contrast, Star Count) pro (frame, tile)
- Phase 9: TILE_RECONSTRUCTION —
W_f,t = G_f × L_f,t, tile-basierte gewichtete Rekonstruktion mit OLA - Phase 10: STATE_CLUSTERING — 6D State-Vector Clustering (aktiv wenn N ≥ Schwellwert)
- Phase 11: SYNTHETIC_FRAMES — Gewichtete Cluster-Mittelwerte, Frame-Reduktion N → K
Siehe die einzelnen Phase-Dokumente für Details zur Classic-Pipeline.
Phase 0: SCAN_INPUT — Input-Scan, Erkennung und Linearitätsprüfung¶
C++ Implementierung:
runner_pipeline.cppPhase-Enum:Phase::SCAN_INPUT
Phase 0 ist die Eingangsphase der Pipeline. Sie liest den ersten Frame, erkennt den Bildmodus und das Bayer-Pattern, führt eine optionale Linearitätsprüfung durch und bereitet die Run-Infrastruktur vor.
┌─────────────────────────────────────────────────────────────┐
│ INPUT: Verzeichnis mit FITS-Frames (*.fit*) │
└────────────────────────────┬────────────────────────────────┘
│
┌──────────────▼───────────────┐
│ 1. Frame-Discovery │
│ core::discover_frames() │
│ Sortierung + Limit │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 2. Run-Verzeichnis anlegen │
│ runs/<run_id>/ │
│ ├── logs/ │
│ ├── outputs/ │
│ └── artifacts/ │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 3. Erster Frame lesen │
│ • Dimensionen (W×H) │
│ • NAXIS │
│ • FITS-Header │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 4. Modus-Erkennung │
│ • MONO vs. OSC │
│ • Bayer-Pattern │
│ (RGGB, GRBG, etc.) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ 5. Linearitätsprüfung │
│ • Stichprobe samplen │
│ • validate_linearity() │
│ • Rejection oder Warnung │
└──────────────┬──────────────┘
│
┌──────────────▼───────────────┐
│ OUTPUT: │
│ • frames[] (validiert) │
│ • ColorMode, BayerPattern │
│ • width, height │
│ • linearity_info JSON │
└──────────────────────────────┘
Detaillierter Ablauf¶
1. Frame-Discovery und Sortierung¶
auto frames = core::discover_frames(in_dir, "*.fit*");
std::sort(frames.begin(), frames.end());
if (max_frames > 0 && frames.size() > max_frames)
frames.resize(max_frames);
- Sucht alle Dateien im Input-Verzeichnis die
*.fit*matchen (FITS, FIT, FITS) - Sortiert alphabetisch für deterministische Reihenfolge
- Optional: Beschränkung auf
--max-framesFrames (Debug/Test) - Abbruch wenn keine Frames gefunden
2. Run-Infrastruktur¶
std::string run_id = core::get_run_id(); // Zeitstempel-basierte ID
fs::path run_dir = runs / run_id;
fs::create_directories(run_dir / "logs");
fs::create_directories(run_dir / "outputs");
fs::create_directories(run_dir / "artifacts");
- Eindeutige Run-ID (Zeitstempel-basiert)
- Konfiguration wird als
config.yamlin den Run-Ordner kopiert - Event-Log-Datei:
run_events.jsonl(TeeBuf → stdout + Datei gleichzeitig)
3. Erster Frame — Dimensionen und Header¶
auto [width, height, naxis] = io::get_fits_dimensions(frames.front());
auto first = io::read_fits_float(frames.front());
first_frame = std::move(first.first); // Matrix2Df (Eigen)
first_header = std::move(first.second); // FitsHeader
- Liest den ersten Frame vollständig ein
- Extrahiert Bildbreite, Bildhöhe und NAXIS
- Speichert den Frame als
first_framefür spätere Verwendung - Speichert den FITS-Header für Output-Dateien
4. Farbmodus-Erkennung¶
detected_mode = io::detect_color_mode(first_header, naxis);
detected_bayer = io::detect_bayer_pattern(first_header);
| Modus | Erkennung | Verhalten |
|---|---|---|
| MONO | NAXIS=2 oder kein BAYERPAT | Einzelkanal-Verarbeitung |
| OSC | NAXIS=2 + BAYERPAT vorhanden | CFA-aware Verarbeitung |
- Konfig-Override: Wenn
data.color_modein config gesetzt, wird bei Abweichung gewarnt - Bayer-Pattern: RGGB, GRBG, GBRG, BGGR — wird aus FITS-Header
BAYERPATgelesen - Bei unbekanntem Pattern: Warnung, Fallback auf RGGB
5. Linearitätsprüfung¶
Die Linearitätsprüfung validiert, dass die Frames keine nichtlinearen Operationen (Stretch, Curves) erfahren haben.
if (cfg.linearity.enabled || cfg.data.linear_required) {
auto indices = core::sample_indices(frames.size(), cfg.linearity.max_frames);
for (size_t idx : indices) {
auto res = metrics::validate_linearity_frame(frame_img, cfg.linearity.strictness);
if (!res.is_linear) {
rejected_indices.push_back(idx);
}
}
}
Konfigurationsparameter¶
| Parameter | Beschreibung | Default |
|---|---|---|
linearity.enabled |
Linearitätsprüfung aktivieren | true |
linearity.max_frames |
Maximale Stichprobengröße | 10 |
linearity.strictness |
Strictness-Level für Validierung | 0.5 |
linearity.min_overall_linearity |
Mindest-Linearitäts-Score | 0.8 |
data.linear_required |
Nicht-lineare Frames entfernen | true |
Verhalten bei nicht-linearen Frames¶
linear_required |
Verhalten |
|---|---|
true |
Nicht-lineare Frames werden aus frames[] entfernt |
false |
Warnung, Frames bleiben in der Pipeline |
- Bei
linear_required=trueund alle Frames rejected: Pipeline bricht mit Error ab - Linearity-Info wird als JSON in das
scan_extraEvent geschrieben
Linearity-Info JSON¶
{
"enabled": true,
"sampled_frames": 10,
"overall_linearity": 0.9,
"min_overall_linearity": 0.8,
"failed_frames": 1,
"failed_frame_names": ["frame_0023.fit"],
"flagged_indices": [23],
"action": "removed",
"frames_remaining": 99
}
CHANNEL_SPLIT (Phase 3 — Metadaten-Phase)¶
Direkt nach SCAN_INPUT wird Phase::CHANNEL_SPLIT emittiert. In der C++ Implementierung ist dies eine reine Metadaten-Phase — die eigentliche Kanaltrennung erfolgt deferred während der Normalisierung und Tile-Verarbeitung.
if (detected_mode == ColorMode::OSC) {
extra["mode"] = "OSC";
extra["channels"] = {"R", "G", "B"};
extra["bayer_pattern"] = detected_bayer_str;
extra["note"] = "deferred_to_tile_processing";
} else {
extra["mode"] = "MONO";
extra["channels"] = {"L"};
}
Bei OSC-Daten bleibt das CFA-Mosaik bis zum Debayer in Phase 13 intakt. Die kanalgetrennte Verarbeitung geschieht implizit über Bayer-Offsets in der Normalisierung.
Fehlerbehandlung¶
| Fehler | Verhalten |
|---|---|
| Input-Verzeichnis existiert nicht | Sofortiger Abbruch (return 1) |
| Keine FITS-Frames gefunden | Sofortiger Abbruch (return 1) |
| Erster Frame nicht lesbar | phase_end(error) → run_end(error) → return 1 |
| Alle Frames non-linear | phase_end(error) → run_end(error) → return 1 |
| Config/Header Mismatch | Warnung, Pipeline läuft weiter |
Event-Emitter-Aufrufe¶
run_start(run_id, {config_path, input_dir, run_dir, frames_discovered, dry_run})
phase_start(SCAN_INPUT)
[warnings: linearity, mode mismatch]
phase_end(SCAN_INPUT, "ok", {input_dir, frames_scanned, image_width, image_height,
color_mode, bayer_pattern, linearity})
phase_start(CHANNEL_SPLIT)
phase_end(CHANNEL_SPLIT, "ok", {mode, channels, bayer_pattern})
Nächste Phase¶
→ Phase 1/2: REGISTRATION + PREWARP, danach Phase 4: NORMALIZATION — Hintergrund-Normalisierung