Output Products
pyKOSMOS++ generates a standardized set of output products organized in a structured directory hierarchy. This page documents the format and content of all output files.
Directory Structure
The pipeline creates the following output directory structure:
output_dir/
├── calibrations/
│ ├── master_bias.fits
│ ├── master_flat.fits
│ └── bad_pixel_mask.fits
├── reduced_2d/
│ ├── science_001_2d.fits
│ ├── science_002_2d.fits
│ └── ...
├── spectra_1d/
│ ├── science_001_trace1.fits
│ ├── science_001_trace2.fits
│ ├── science_002_trace1.fits
│ └── ...
├── wavelength_solutions/
│ ├── wavelength_solution_arc001.fits
│ └── ...
├── quality_reports/
│ ├── science_001_quality.yaml
│ ├── science_002_quality.yaml
│ └── summary_report.txt
├── diagnostic_plots/
│ ├── wavelength_solution.png
│ ├── science_001_2d.png
│ ├── science_001_trace1_profile.png
│ └── ...
└── logs/
└── reduction_log.txt
Calibration Products
master_bias.fits
Combined master bias frame.
Format: FITS image (Primary HDU)
Header Keywords:
NAXIS = 2
NAXIS1 = 2048 / Spectral axis
NAXIS2 = 515 / Spatial axis
BUNIT = 'ADU'
# Calibration metadata
NCOMBINE= 5 / Number of bias frames combined
COMBMETH= 'median' / Combination method
BIASLVL = 389.2 / Median bias level (ADU)
BIASSTD = 3.5 / Bias standard deviation (ADU)
# Provenance
PIPELINE= 'pyKOSMOS++ v0.1.0'
DATE = '2025-01-15T10:23:45'
PROCSTEP= 'BIAS_COMBINE'
Data: 2D float32 array, bias level in ADU
Usage:
from astropy.io import fits
with fits.open('master_bias.fits') as hdul:
bias_data = hdul[0].data
bias_level = hdul[0].header['BIASLVL']
master_flat.fits
Normalized master flat field.
Format: FITS image (Primary HDU)
Header Keywords:
NAXIS = 2
BUNIT = 'normalized'
NCOMBINE= 3 / Number of flat frames combined
COMBMETH= 'median'
NORMLVL = 1.0 / Normalization level
BADPIXFR= 0.0023 / Bad pixel fraction
PIPELINE= 'pyKOSMOS++ v0.1.0'
PROCSTEP= 'FLAT_COMBINE'
Data: 2D float32 array, normalized response (median = 1.0)
Bad Pixel Mask: Pixels with response <0.5 or >1.5 are flagged
bad_pixel_mask.fits
Boolean mask of bad pixels.
Format: FITS image (Primary HDU)
Data: 2D boolean array (0=good, 1=bad)
Bad pixel criteria:
Flat response <0.5 or >1.5 (vignetting or hot pixels)
Saturated pixels in calibration frames
Cosmic ray hits in master flat
Reduced 2D Spectra
science_NNN_2d.fits
Calibrated 2D spectrum with trace information.
Format: Multi-extension FITS
Extension 0 (Primary): Calibrated 2D spectrum
NAXIS = 2
BUNIT = 'ADU'
# Original frame metadata
OBJECT = 'NGC1234'
EXPTIME = 1200.0 / seconds
AIRMASS = 1.15
DATEOBS = '2024-01-15T05:30:00'
# Calibration applied
BIASFILE= 'master_bias.fits'
FLATFILE= 'master_flat.fits'
BIASCORR= T
FLATCORR= T
COSMCORR= T / Cosmic ray cleaning applied
PIPELINE= 'pyKOSMOS++ v0.1.0'
PROCSTEP= '2D_CALIBRATION'
Extension 1 (VARIANCE): Variance array
Propagated from read noise and Poisson statistics
Same shape as primary data
Extension 2 (MASK): Pixel mask
0 = good pixel
1 = bad pixel from flat
2 = cosmic ray detected
4 = saturation
8 = other (can combine bits)
Extension 3 (TRACES): Binary table of detected traces
Columns:
TRACE_ID(int): Trace identifierSPATIAL_POS(float array): Spatial center positions [pixels]SPECTRAL_PIX(float array): Spectral pixel coordinatesSNR_EST(float): Estimated signal-to-noise ratio
Usage:
with fits.open('science_001_2d.fits') as hdul:
data = hdul[0].data
variance = hdul[1].data
mask = hdul[2].data
traces = hdul[3].data
Extracted 1D Spectra
science_NNN_traceM.fits
Extracted and wavelength-calibrated 1D spectrum.
Format: Multi-extension FITS (compatible with Astropy Spectrum1D)
Extension 0 (Primary): Header only
# Source information
OBJECT = 'NGC1234'
TRACEID = 1
EXPTIME = 1200.0
AIRMASS = 1.15
# Extraction metadata
EXTMETH = 'optimal' / Extraction method
APWIDTH = 10 / Aperture width (pixels)
SKYBUFFE= 30 / Sky buffer distance (pixels)
# Quality metrics
MED_SNR = 12.5 / Median signal-to-noise ratio
WLRMS = 0.08 / Wavelength RMS residual (Angstroms)
GRADE = 'Good' / Quality grade
PIPELINE= 'pyKOSMOS++ v0.1.0'
PROCSTEP= '1D_EXTRACTION'
Extension 1 (FLUX): 1D flux array
NAXIS: 1
NAXIS1: Number of wavelength points
BUNIT: ‘ADU’ or ‘erg/s/cm^2/Angstrom’ (if flux calibrated)
Extension 2 (WAVELENGTH): Wavelength array
BUNIT: ‘Angstrom’
WAVEMIN, WAVEMAX: Wavelength range
Extension 3 (UNCERTAINTY): 1D uncertainty array
Propagated from variance in 2D spectrum
Same units as flux
Extension 4 (MASK): 1D mask array
0 = good pixel
Nonzero = various flags (cosmic ray, saturation, etc.)
Usage:
from specutils import Spectrum1D
from astropy import units as u
# Load as Spectrum1D
spectrum = Spectrum1D.read('science_001_trace1.fits')
# Access data
flux = spectrum.flux # with units
wavelength = spectrum.spectral_axis
uncertainty = spectrum.uncertainty
# Or use FITS directly
with fits.open('science_001_trace1.fits') as hdul:
flux = hdul['FLUX'].data
wavelength = hdul['WAVELENGTH'].data
uncertainty = hdul['UNCERTAINTY'].data
Wavelength Solutions
wavelength_solution_arcNNN.fits
Wavelength calibration solution for arc frame.
Format: FITS binary table (Primary HDU)
Columns:
PIXEL(float): Pixel positionWAVELENGTH(float): Wavelength in AngstromsRESIDUAL(float): Fit residual in AngstromsINTENSITY(float): Line intensityUSED(bool): Line included in fit (not clipped)
Header Keywords:
LAMPTYPE= 'HeNeAr' / Arc lamp type
POLYORD = 5 / Polynomial order
NLINES = 45 / Total lines identified
NUSED = 42 / Lines used in fit
RMS = 0.078 / RMS residual (Angstroms)
WAVEMIN = 3650.0 / Minimum wavelength (Angstroms)
WAVEMAX = 7200.0 / Maximum wavelength (Angstroms)
# Polynomial coefficients
COEFF0 = 3645.123
COEFF1 = 1.0234
COEFF2 = -0.000123
...
Usage:
with fits.open('wavelength_solution_arc001.fits') as hdul:
table = hdul[1].data
header = hdul[0].header
# Extract coefficients
order = header['POLYORD']
coeffs = [header[f'COEFF{i}'] for i in range(order+1)]
# Reconstruct solution
from numpy.polynomial.chebyshev import chebval
wavelength = chebval(pixel, coeffs)
Quality Reports
science_NNN_quality.yaml
Detailed quality metrics for each science frame.
Format: YAML
Structure:
# Source metadata
source_file: science_001.fits
object_name: NGC1234
exposure_time: 1200.0
observation_date: '2024-01-15T05:30:00'
# Calibration quality
calibration:
bias_level: 389.2
bias_stdev: 3.5
flat_bad_pixel_fraction: 0.0023
cosmic_ray_fraction: 0.0087
# Wavelength solution
wavelength:
polynomial_order: 5
rms_residual: 0.078
n_lines_identified: 45
n_lines_used: 42
wavelength_range: [3650.0, 7200.0]
# Extraction metrics (per trace)
traces:
- trace_id: 1
median_snr: 12.5
peak_snr: 45.2
spatial_center: 257.5
profile_consistency: 0.92
flux_conservation: 0.98
- trace_id: 2
median_snr: 8.3
...
# Overall assessment
overall_grade: Good
quality_flags: []
warnings:
- 'Trace 2 has lower SNR (8.3 < 10.0 recommended)'
# Processing metadata
pipeline_version: '0.1.0'
processing_date: '2025-01-15T10:45:00'
processing_time_seconds: 125.3
Usage:
import yaml
with open('science_001_quality.yaml') as f:
quality = yaml.safe_load(f)
print(f"Overall grade: {quality['overall_grade']}")
for trace in quality['traces']:
print(f"Trace {trace['trace_id']}: SNR = {trace['median_snr']:.1f}")
summary_report.txt
Human-readable summary of all processed frames.
Format: Plain text
Example:
pyKOSMOS++ Pipeline Summary Report
==================================
Processing Date: 2025-01-15 10:45:00
Pipeline Version: 0.1.0
Input Directory: data/2024-01-15
Output Directory: reduced/2024-01-15
Calibration Summary:
-------------------
Master Bias: 5 frames combined, level=389.2±3.5 ADU
Master Flat: 3 frames combined, bad pixels=0.23%
Arc Lamp: HeNeAr, 45 lines identified, RMS=0.078 Å
Science Frames Processed: 10
----------------------------
Frame Traces Grade Median SNR Wavelength RMS
science_001.fits 2 Good 12.5 0.078 Å
science_002.fits 2 Excellent 23.1 0.065 Å
science_003.fits 1 Fair 7.2 0.092 Å
...
Overall Statistics:
------------------
Total spectra extracted: 15
Mean SNR: 14.8
Grade distribution:
Excellent: 3 (20%)
Good: 10 (67%)
Fair: 2 (13%)
Poor: 0 (0%)
Processing Time: 2.3 minutes (13.8 seconds/frame)
Diagnostic Plots
wavelength_solution.png
Wavelength calibration diagnostic.
Contents:
Top panel: Wavelength vs pixel with Chebyshev fit
Bottom panel: Fit residuals with RMS threshold lines
Format: PNG, 14x8 inches, 300 DPI
science_NNN_2d.png
Calibrated 2D spectrum with detected traces.
Contents:
2D spectrum with log-scale colormap
Overlaid trace positions (red lines)
Wavelength and spatial axes labeled
Format: PNG, 14x6 inches, 300 DPI
science_NNN_traceM_profile.png
Spatial profile fit diagnostic.
Contents:
Top panel: Data vs fitted profile (Gaussian/Moffat)
Bottom panel: Fit residuals
Format: PNG, 10x6 inches, 300 DPI
science_NNN_traceM_1d.png
Extracted 1D spectrum.
Contents:
Flux vs wavelength
Major spectral features annotated (if known)
Format: PNG, 14x4 inches, 300 DPI
Logs
reduction_log.txt
Detailed processing log.
Format: Plain text with timestamps
Example:
2025-01-15 10:30:00 INFO: pyKOSMOS++ v0.1.0 starting
2025-01-15 10:30:00 INFO: Input directory: data/2024-01-15
2025-01-15 10:30:01 INFO: Discovered 5 bias, 3 flat, 1 arc, 10 science frames
2025-01-15 10:30:02 INFO: Creating master bias from 5 frames
2025-01-15 10:30:05 INFO: Master bias: level=389.2±3.5 ADU
2025-01-15 10:30:05 INFO: Creating master flat from 3 frames
2025-01-15 10:30:12 INFO: Master flat: bad pixels=0.23%
2025-01-15 10:30:13 INFO: Fitting wavelength solution
2025-01-15 10:30:15 INFO: Wavelength solution: order=5, RMS=0.078 Å, 45 lines
2025-01-15 10:30:16 INFO: Processing science_001.fits
2025-01-15 10:30:18 INFO: Detected 2 traces
2025-01-15 10:30:25 INFO: Trace 1: SNR=12.5, extracted
2025-01-15 10:30:30 INFO: Trace 2: SNR=8.3, extracted
2025-01-15 10:30:30 INFO: Overall grade: Good
...
2025-01-15 10:32:45 INFO: Pipeline completed successfully
2025-01-15 10:32:45 INFO: Total time: 2.75 minutes
Reading Output Products
Python Example
Complete workflow to read and analyze outputs:
from pathlib import Path
from astropy.io import fits
from specutils import Spectrum1D
import yaml
import matplotlib.pyplot as plt
output_dir = Path('reduced/2024-01-15')
# Read quality report
with open(output_dir / 'quality_reports/science_001_quality.yaml') as f:
quality = yaml.safe_load(f)
print(f"Overall grade: {quality['overall_grade']}")
print(f"Median SNR: {quality['traces'][0]['median_snr']:.1f}")
# Load 1D spectrum
spectrum_file = output_dir / 'spectra_1d/science_001_trace1.fits'
spectrum = Spectrum1D.read(spectrum_file)
# Plot spectrum
plt.figure(figsize=(12, 4))
plt.plot(spectrum.spectral_axis, spectrum.flux)
plt.xlabel(f'Wavelength ({spectrum.spectral_axis.unit})')
plt.ylabel(f'Flux ({spectrum.flux.unit})')
plt.title(f"{quality['object_name']} - Grade: {quality['overall_grade']}")
plt.show()
# Load 2D spectrum
with fits.open(output_dir / 'reduced_2d/science_001_2d.fits') as hdul:
data_2d = hdul[0].data
variance = hdul[1].data
mask = hdul[2].data
traces = hdul[3].data
print(f"Detected {len(traces)} traces")
for trace in traces:
print(f" Trace {trace['TRACE_ID']}: SNR={trace['SNR_EST']:.1f}")
See Also
CLI Reference - Generating output products
Python API - Programmatic access to outputs
quickstart - Example workflows