COMMON_OVERLAP + SYNTHETIC_FRAMES + STACKING + VALIDATION + DEBAYER + ASTROMETRY + BGE + PCC — Finales Stacking und Output¶
C++ Implementierung:
runner_pipeline.cpp,runner_phase_registration.cpp(aktueller v3.3 Stand) Phase-Enums:COMMON_OVERLAP(7),SYNTHETIC_FRAMES(11),STACKING(12), Validation (kein Enum),DEBAYER(13),ASTROMETRY(14),BGE(15, optional),PCC(16),DONE(17)
Übersicht¶
Die letzten Phasen der Pipeline arbeiten auf den vorregistrierten/prewarped Frames, berücksichtigen den gemeinsamen Bildbereich (Common Overlap), stacken das finale Signal, führen optional BGE vor PCC aus und erzeugen die finalen FITS-Outputs. Für v3.3.9 sind dabei besonders wichtig:
- support-aware Gültigkeitssemantik an Rand- und Canvas-Bereichen,
- massenerhaltende Cluster-Semantik im Full Mode,
- BGE als strikt additiver Schritt vor PCC.
┌──────────────────────────────────────────────────────┐
│ PREWARP (Phase 2, vorgelagert) │
│ • Alle Frames auf globalen Canvas gewarpt │
│ • CFA-safe Warp für OSC (Subplane-Methode) │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ COMMON_OVERLAP (Phase 7) │
│ • Pixelweise Valid-Map über alle Frames │
│ • common_overlap.json + Tile-Valid-Fraktionen │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ SYNTHETIC_FRAMES (Phase 11) │
│ • Pro Cluster: gewichtetes Mittel → synth Frame │
│ • Cluster-Masse M_k bleibt relevant │
│ • N Original-Frames → K synthetische Frames │
│ (übersprungen im Reduced Mode) │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ STACKING (Phase 12) │
│ • Sigma-Clipping Rejection ODER Mean │
│ • massenerhaltende Cluster-Aggregation │
│ • Output-Skalierung: × median_bg + pedestal │
│ • stacked.fits + reconstructed_L.fit │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ VALIDATION │
│ • FWHM-Verbesserung check │
│ • Tile-Weight-Varianz check │
│ • Tile-Pattern-Detektion (Sobel) │
│ → validation.json │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ DEBAYER (Phase 13) — nur OSC │
│ • Nearest-Neighbor Demosaic → R, G, B │
│ • Canvas/Crop-Offsets für korrekte Bayer-Parität │
│ • stacked_rgb.fits (3-Plane FITS-Cube) │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ ASTROMETRY (Phase 14) │
│ • ASTAP solve / WCS │
│ • WCS-Header in RGB-Outputs │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ BGE (Phase 15) │
│ • Background Gradient Extraction vor PCC │
│ • Direkte Subtraktion auf R/G/B │
│ • artifacts/bge.json (Diagnostik) │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ PCC (Phase 16) │
│ • Photometric Color Calibration │
│ • pcc_R/G/B.fit + stacked_rgb_pcc.fits │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ DONE (Phase 17) │
│ • run_end(ok) oder run_end(validation_failed) │
└──────────────────────────────────────────────────────┘
Canvas- und Common-Overlap-Kontext¶
- PREWARP schreibt alle Frames auf einen globalen Canvas (
canvas_width,canvas_height,tile_offset_x/y). - COMMON_OVERLAP ermittelt den tatsächlich gemeinsamen, datentragenden Pixelbereich über alle nutzbaren Frames.
- In
TILE_RECONSTRUCTION/STACKINGwerden Pixel/Tiles gemäß stacking.common_overlap_required_fractionstacking.tile_common_valid_min_fractiongefiltert, damit Rand-/Leerbereiche den Stack nicht verfälschen.v3.3.9verschärft das zu einer support-aware Semantik:- canvas-ungültige Pixel zählen nicht als gültige Samples,
|V| = 0darf keinen OLA- oder Stack-Nenner aufblasen,- Fill-Zeros sind reine Platzhalter, keine Daten.
- Bei OSC müssen Canvas-/Crop-Offsets Bayer-paritätskonsistent in Normalisierung/Output-Skalierung durchgereicht werden.
Phase 11: SYNTHETIC_FRAMES¶
Funktionsweise¶
Pro Cluster wird ein global-gewichtetes Mittel aller zugehörigen Frames berechnet:
auto reconstruct_subset = [&](const std::vector<char> &frame_mask) -> Matrix2Df {
for (size_t fi = 0; fi < frames.size(); ++fi) {
if (!frame_mask[fi]) continue;
auto pair = load_frame_normalized(fi);
Matrix2Df img = apply_global_warp(pair.first, fi);
warped.push_back(std::move(img));
weights_subset.push_back(global_weights[fi]);
}
Matrix2Df out = Matrix2Df::Zero(rows, cols);
for (size_t i = 0; i < warped.size(); ++i)
out += warped[i] * (weights_subset[i] / wsum);
return out;
};
Cluster-Filterung¶
for (int c = 0; c < n_clusters; ++c) {
// Nur Cluster mit genug Frames
if (count < synth_min) continue;
Matrix2Df syn = reconstruct_subset(use_frame);
synthetic_frames.push_back(syn);
if (synthetic_frames.size() >= synth_max) break;
}
- Mindestgröße: Cluster mit
count < frames_minwerden übersprungen - Maximum: Höchstens
frames_maxsynthetische Frames - Synthetische Frames werden als
synthetic_*.fitgespeichert (mit Output-Skalierung)
Skip-Bedingungen¶
| Bedingung | Verhalten |
|---|---|
| Reduced Mode | Phase skipped, use_synthetic_frames = false |
| Keine Frames ≥ frames_min | Skipped wenn N < frames_min, Error sonst |
| Alle Cluster zu klein | Error, Pipeline-Abbruch |
Konfiguration¶
| Parameter | Beschreibung | Default |
|---|---|---|
synthetic.frames_min |
Minimale Cluster-Größe | 3 |
synthetic.frames_max |
Maximale Anzahl synth. Frames | 30 |
Artifact: synthetic_frames.json¶
Phase 12: STACKING¶
Sigma-Clipping Rejection Stacking¶
if (use_synthetic_frames) {
if (cfg.stacking.method == "rej") {
recon = reconstruction::sigma_clip_stack(
synthetic_frames,
cfg.stacking.sigma_clip.sigma_low,
cfg.stacking.sigma_clip.sigma_high,
cfg.stacking.sigma_clip.max_iters,
cfg.stacking.sigma_clip.min_fraction);
} else {
// Mean stacking
for (const auto &sf : synthetic_frames) recon += sf;
recon /= synthetic_frames.size();
}
}
| Methode | Config | Beschreibung |
|---|---|---|
| rej | stacking.method: "rej" |
Sigma-Clipping Rejection (Standard) |
| mean | stacking.method: "mean" |
Einfaches Mittel |
Sigma-Clipping Parameter¶
| Parameter | Beschreibung | Default |
|---|---|---|
stacking.sigma_clip.sigma_low |
Untere σ-Grenze | 2.5 |
stacking.sigma_clip.sigma_high |
Obere σ-Grenze | 3.0 |
stacking.sigma_clip.max_iters |
Maximale Iterationen | 5 |
stacking.sigma_clip.min_fraction |
Mindestanteil beibehaltener Pixel | 0.5 |
Reduced Mode¶
Wenn use_synthetic_frames = false, wird das Rekonstruktionsergebnis aus Phase 9 direkt als finales Bild übernommen — kein erneutes Stacking.
Wenn use_synthetic_frames = true, ist die v3.3.9-Semantik der Cluster-Aggregation:
- Clusterqualität
Q_kbleibt relevant, - zusätzlich wird die Cluster-Masse
M_k = sum_{f in k} G_ferhalten, - die finale Gewichtung der Cluster ist damit nicht nur qualitäts-, sondern auch massenerhaltend.
Output-Skalierung¶
Matrix2Df recon_out = recon;
if (detected_mode == ColorMode::OSC) {
const float bg_luma = 0.25f * output_bg_r + 0.5f * output_bg_g + 0.25f * output_bg_b;
recon_out *= bg_luma;
recon_out.array() += output_pedestal;
} else {
// MONO: origin muss Canvas/Crop-Offset enthalten.
image::apply_output_scaling_inplace(
recon_out,
-debayer_tile_offset_x,
-debayer_tile_offset_y,
detected_mode,
detected_bayer_str,
output_bg_mono, output_bg_r, output_bg_g, output_bg_b,
output_pedestal);
}
io::write_fits_float(run_dir / "outputs" / "stacked.fits", recon_out, first_hdr);
io::write_fits_float(run_dir / "outputs" / "reconstructed_L.fit", recon_out, first_hdr);
Die Skalierung konvertiert normalisierte Werte zurück in physikalische Einheiten:
| Modus | Skalierung |
|---|---|
| MONO | pixel = pixel × output_bg_mono + 32768 |
| OSC (Stack-L) | pixel = pixel × (0.25·bg_r + 0.5·bg_g + 0.25·bg_b) + 32768 |
- output_bg_*: Median der Background-Werte über alle Frames (aus NORMALIZATION, Phase 4)
- Pedestal 32768: Verhindert negative Werte in FITS (16-bit compatible)
Validation¶
Die Validierung prüft die Qualität des Rekonstruktionsergebnisses:
1. FWHM-Verbesserung¶
float output_fwhm_med = metrics::measure_fwhm_from_image(recon);
float improvement = (seeing_fwhm - output_fwhm) / seeing_fwhm * 100.0f;
v["fwhm_improvement_ok"] = (improvement >= cfg.validation.min_fwhm_improvement_percent);
- Vergleich: Seeing-FWHM (Phase 4) vs. Output-FWHM
- Erwartung: Output-FWHM sollte kleiner sein (besseres Seeing durch Stacking)
2. Tile-Weight-Varianz¶
// Mittleres effektives Gewicht pro Tile
for (size_t ti = 0; ti < tiles.size(); ++ti) {
float mean_W = Σ (G_f × L_f,t) / N;
tile_means.push_back(mean_W);
}
// Normalisierte Varianz: Var(tile_means) / mean(tile_means)²
- Prüft: Gibt es genug Variation in den Tile-Gewichten?
- Zu niedrige Varianz → Gewichtung hat keinen Effekt → Warnung
3. Tile-Pattern- und Boundary-Detektion¶
// Sobel-Gradient an Tile-Grenzen vs. Nachbar-Pixel
cv::Sobel(img_cv, gx, CV_32F, 1, 0, 3);
cv::Sobel(img_cv, gy, CV_32F, 0, 1, 3);
cv::magnitude(gx, gy, mag);
for (int x : tile_boundaries_x) {
float boundary_grad = line_mean_x(x);
float neighbor_grad = 0.5 * (line_mean_x(x-2) + line_mean_x(x+2));
float ratio = boundary_grad / neighbor_grad;
worst_ratio = max(worst_ratio, ratio);
}
tile_pattern_ok = (worst_ratio < 1.5f);
- Prüft: Sind Tile-Grenzen im finalen Bild sichtbar?
- Vergleicht Sobel-Gradient an Tile-Grenzen mit benachbarten Pixeln
- Ratio > 1.5: Tile-Pattern erkannt → Warnung
- Nur wenn
validation.require_no_tile_pattern = true
v3.3.9 ergänzt zusätzlich die stärkere Anforderung, dass support-aware OLA alle gültigen Randpixel abdecken und die Partition-of-Unity-Toleranz erfüllen muss. Die Validierung ist also nicht nur bildästhetisch, sondern auch support-semantisch motiviert.
Validation-Ergebnis¶
if (!validation_ok) {
run_validation_failed = true;
// Pipeline läuft weiter (DEBAYER wird noch ausgeführt!)
}
- Validation-Fehler bricht die Pipeline nicht ab
- DEBAYER wird immer noch ausgeführt (GUI braucht die Outputs)
- Run wird am Ende als
"validation_failed"markiert
Artifact: validation.json¶
{
"seeing_fwhm_median": 3.5,
"output_fwhm_median": 2.8,
"fwhm_improvement_percent": 20.0,
"fwhm_improvement_ok": true,
"tile_weight_variance": 0.15,
"tile_weight_variance_ok": true,
"tile_pattern_ratio": 1.02,
"tile_pattern_ok": true
}
Phase 13: DEBAYER¶
OSC-Modus¶
if (detected_mode == ColorMode::OSC) {
auto debayer = image::debayer_nearest_neighbor(recon, detected_bayer);
Matrix2Df R_out = debayer.R * output_bg_r + output_pedestal;
Matrix2Df G_out = debayer.G * output_bg_g + output_pedestal;
Matrix2Df B_out = debayer.B * output_bg_b + output_pedestal;
io::write_fits_float("reconstructed_R.fit", R_out, first_hdr);
io::write_fits_float("reconstructed_G.fit", G_out, first_hdr);
io::write_fits_float("reconstructed_B.fit", B_out, first_hdr);
io::write_fits_rgb("stacked_rgb.fits", R_out, G_out, B_out, first_hdr);
}
- Nearest-Neighbor Demosaic: Deterministisch, schnell
- Per-Kanal Output-Skalierung: Jeder Kanal × eigener Median-Background + Pedestal
- Offset-Korrektheit: Debayer-/Crop-Offsets werden in die Skalierung eingebracht, um Bayer-Paritätsfehler (R/G-Vertauschung) zu vermeiden.
- stacked_rgb.fits: 3-Plane FITS-Cube (NAXIS3=3) für direkte RGB-Anzeige
MONO-Modus¶
- Phase wird als "ok" mit
mode: "MONO"beendet - Keine Demosaic-Operation notwendig
- Output ist bereits in
stacked.fits/reconstructed_L.fit
Output-Dateien¶
| Datei | Modus | Beschreibung |
|---|---|---|
stacked.fits |
Beide | Rekonstruiertes Bild (mit Skalierung) |
reconstructed_L.fit |
Beide | Identisch zu stacked.fits |
reconstructed_R.fit |
OSC | Roter Kanal |
reconstructed_G.fit |
OSC | Grüner Kanal |
reconstructed_B.fit |
OSC | Blauer Kanal |
stacked_rgb.fits |
OSC | RGB-Cube (NAXIS3=3) |
stacked_rgb_solve.fits |
OSC | Lineares RGB für Astrometrie/WCS |
stacked_rgb_bge.fits |
OSC | BGE-Output vor PCC (direkter Vergleichspfad) |
stacked_rgb_pcc.fits |
OSC | PCC-kalibrierter RGB-Cube |
pcc_R.fit |
OSC | PCC-kalibrierter R-Kanal |
pcc_G.fit |
OSC | PCC-kalibrierter G-Kanal |
pcc_B.fit |
OSC | PCC-kalibrierter B-Kanal |
synthetic_*.fit |
Normal | Synthetische Frames (mit Skalierung) |
Phase 14: ASTROMETRY¶
Wenn aktiviert und RGB-Daten vorhanden sind, wird ein ASTAP-Plate-Solve ausgeführt. Bei Erfolg:
- WCS wird aus
.wcsgeparst - WCS-Header in
first_hdrinjiziert stacked_rgb.fitsundstacked_rgb_solve.fitsmit WCS neu geschriebenartifacts/stacked_rgb.wcsabgelegt
Bei Fehlschlag wird die Phase als skipped mit Grund (astap_not_found, solve_failed, etc.) beendet.
BGE (optional, zwischen ASTROMETRY und PCC)¶
BGE wird ausgeführt, wenn:
cfg.bge.enabled = true- RGB-Daten vorhanden sind (
have_rgb) - tile-basierte Metrik-/Grid-Daten verfügbar und konsistent sind
Wirkung:
- Die modellierte großskalige Hintergrundkomponente wird direkt von R/G/B subtrahiert.
- PCC sieht dadurch bereits gradientenkorrigierte RGB-Daten.
v3.3.9 präzisiert dabei:
- BGE ist strikt additiv,
- BGE ist kanalweise,
- BGE darf keine Core-Gewichte (
G,L,W) verändern, - canvas-ungültige Pixel dürfen nicht in BGE-Samples eingehen.
Artifact:
artifacts/bge.jsonmit kanalweisen Diagnosedaten:- Sample- und Gewichtsstatistiken
- Grid-Zellen inkl. Hintergrundwerten
- Residuenstatistiken und Werteverteilungen
Phase 16: PCC¶
PCC läuft nur, wenn:
cfg.pcc.enabled = true- WCS aus ASTROMETRY vorhanden
- RGB-Daten vorhanden
Outputs bei Erfolg:
outputs/pcc_R.fitoutputs/pcc_G.fitoutputs/pcc_B.fitoutputs/stacked_rgb_pcc.fits
Bei fehlenden Katalogsternen oder Fit-Problemen wird die Phase als skipped beendet.
v3.3.9 ergänzt außerdem einen optionalen post-PCC Cleanup-Schritt für isolierte Chroma-Speckles im expliziten RGB-Bereich. Dieser gehört nicht mehr zum linearen Kern, ist aber als optionale Nachverarbeitung ausdrücklich erlaubt.
Phase 17: DONE¶
emitter.phase_start(run_id, Phase::DONE, "DONE", log_file);
emitter.phase_end(run_id, Phase::DONE, "ok", {}, log_file);
if (run_validation_failed) {
emitter.run_end(run_id, false, "validation_failed", log_file);
return 1;
}
emitter.run_end(run_id, true, "ok", log_file);
return 0;
| Ergebnis | Exit-Code | Status |
|---|---|---|
| Erfolgreich | 0 | "ok" |
| Validation fehlgeschlagen | 1 | "validation_failed" |
| Pipeline-Fehler | 1 | "error" (bei vorherigem Abbruch) |
CLI-Optionen¶
tile_compile_runner run \
--config <path> # config.yaml
--input-dir <path> # FITS Input-Verzeichnis
--runs-dir <path> # Runs-Ausgabe-Verzeichnis
--project-root <path> # Projekt-Root (optional)
--max-frames <n> # Frame-Limit (0 = kein Limit)
--max-tiles <n> # Tile-Limit im Tile-Pfad (ab Phase 6)
--dry-run # Kein Processing
--stdin # Config von stdin lesen