virgil-vlti
Calibration and error models for VLTI interferometry, for use with virgil.
What it does for each VLTI instrument:
| Instrument | What virgil-vlti does | Status | Details |
|---|---|---|---|
| GRAVITY | Calibrates V², T3AMP and T3PHI (and OI_VIS) from calibrator exposures, with nuisance modes; ports ESO's gravity_viscal; calibrator-PCA templates |
Calibration checked against ESO's calibrated products | GRAVITY |
| PIONIER | Measures the error structure of calibrated products (the pair test) and turns it into gain templates | Error model only; no calibration | PIONIER |
| MATISSE | Reads headers, refuses unsafe products, reports them (virgil-vlti-inspect) |
Headers only; calibration planned | MATISSE |
Each page lists what is implemented, what is not, and what each method has been checked against (ESO pipeline code or publications).
virgil fits and images interferometric data from any instrument. This repository holds what depends on one instrument: calibrating pipeline products, empirical error models, and the instrument facts behind them. See PLAN.md for the boundary with virgil and what comes next, PROVENANCE.md for where each method comes from and how it is checked, and design/ for the GRAVITY calibration review and notes on PIONIER and MATISSE.
Install
pip install virgil-vlti # numpy, scipy, astropy: calibration only
pip install "virgil-vlti[virgil]" # and virgil-astro, to fit the output with virgil
For development, clone the repository and install it editable:
git clone https://github.com/benjaminpope/virgil-vlti
cd virgil-vlti
pip install -e ".[dev]"
Quickstart on your own data
Input. The uncalibrated GRAVITY products from ESO's pipeline (the *_VIS files), science and calibrator exposures together in one folder. Download them from the ESO archive; nothing else is needed. Nights are found from the headers.
One command, here with separate calibrator stars (see below for the dual-field and other options):
virgil-vlti-calibrate reduced/ --out calibrated --cal "HD 12345=0.45" --tf-modes
Output, in calibrated/:
calibrated/
index.csv one row per exposure: what was done, warnings, diagnostics
<night>/<target>_<time>.fits calibrated OIFITS (V², T3AMP, T3PHI)
<night>/<target>_<time>.modes.npz calibrator-diameter nuisance modes (with --cal NAME=DIAM±ERR)
<night>/<target>_<time>.tf_modes.npz transfer-function nuisance modes (with --tf-modes)
<night>/<target>_<time>.template_modes.npz calibration-template modes (with --template)
The transfer function (TF) is the instrument's response measured on calibrator stars, which are close to point sources, and divided out of the science data. Its uncertainty is estimated by leave-one-out: each calibrator is predicted from the others, and the prediction errors give the error of the TF at the science exposure's time. The .npz files hold the part of that error (and of the calibrators' diameter errors) that is shared between exposures, as nuisance modes for virgil to marginalise rather than as independent errors.
Fit with virgil, which attaches the modes for you:
import glob
from virgil_vlti import to_oidata
data = to_oidata(
sorted(glob.glob("calibrated/*/*.fits")), "GRAVITY_SC"
) # a virgil OIData with its modes
to_oidata needs virgil-astro>=0.3 and, until 0.4.0 is released, virgil's main branch (pip install git+https://github.com/benjaminpope/virgil). Its modes' units and conventions are in its docstring (load.py).
Calibrating products
Checked against ESO's own calibrated GRAVITY products (four public cases): the V² level agrees to 0.12% (median), the errors exactly, and the closure phases to 0.06°. --match-gravity-viscal is a port of gravity_viscal itself, written from the pipeline's source, and reproduces ESO's products to about 1e-12. It follows the source where the manual differs (e.g. the time weights). See PROVENANCE.md.
virgil-vlti-calibrate calibrates a folder of uncalibrated products night by night. Calibrator exposures give a transfer function per baseline and channel. It is interpolated in time to each science frame and divided out of V², and the calibrator closure phases (averaged as unit vectors) are subtracted. The interpolation's uncertainty is added to the errors.
GRAVITY in dual-field mode, using the fringe-tracking star as an in-field calibrator:
virgil-vlti-calibrate reduced/ --out calibrated --dual-field --window 2.21 2.40 --bin 4
With separate calibrator stars, optionally with uniform-disk diameters in mas:
virgil-vlti-calibrate reduced/ --out calibrated --cal "HD 12345=0.45" --cal "HD 678"
Then in virgil:
import glob
from virgil import OIData
from virgil.oifits import read_oifits
data = OIData(read_oifits(sorted(glob.glob("calibrated/*/*.fits")), insname="GRAVITY_SC"))
Main options (--help for all):
| Option | Default | Meaning |
|---|---|---|
--dual-field, --cal NAME[=DIAM] |
ESO PRO CATG |
how calibrator exposures are recognised |
--insname |
GRAVITY science channel, both polarisations | instruments to calibrate; the fringe tracker is never used |
--window LO HI, --bin N |
all channels, 1 | channel window (µm) and binning |
--interp linear|gaussian |
linear |
linear in time, or Gaussian weights (the pipeline manual's formula, with error floors adapted from the source) with --timescale (1 h) |
--tf-error loo|scatter|propagated|none |
loo |
leave-one-out prediction error of the transfer function, the night's scatter, the calibrators' statistical errors only (with --template, whose modes carry the systematic), or none |
--tf-modes |
off | write the loo or scatter transfer-function error on V² and closure phases as nuisance modes (<file>.tf_modes.npz), one per night and baseline or triangle, shared by that night's files, instead of adding it to each datum's error |
--tf-mode-shape |
poly1 |
with --tf-modes, how each mode's channel shape is smoothed: poly1 or poly2 (a polynomial in wavelength, for a transfer-function error with a colour), flat (its channel mean; underestimates errors at realistic chromatic transfer functions, pull rms 3.1 at colour 0.36 against about 1.3 for poly1; our own Monte Carlo, not yet checked against ESO/VLTI documentation), or raw (the old channel-by-channel estimate, for comparison only) |
--cp-only-when-uncalibrated |
off | for a science file on a night with no calibrator, write a closure-phase-only product instead of skipping it. Its T3PHI is uncalibrated: it keeps the instrumental closure phase and has statistical errors only, so fit it with the template's closure-phase offset modes (--template, t3phi modes only) and treat the errors as a lower bound. V² and T3AMP are flagged, header CPONLY |
--cp-only NAME |
none | the same flagging for science files whose input name, output stem (<target>_<YYYYMMDDTHHMMSS>) or DATE-OBS contains NAME (repeatable; a name that marks nothing is an error). With calibrators the closure phases are calibrated as usual and only V² and T3AMP are set aside |
--vis |
off | also calibrate OI_VIS (VISAMP, VISPHI) |
--smooth-tf DEG |
off | smooth the amplitude transfer function across channels with gravity_viscal's polynomial fit (it uses 5) |
--match-gravity-viscal |
off | calibrate exactly as ESO's gravity_viscal (pipeline 1.9.7) does, to reproduce ESO's calibrated products; with --delta-time-calib, --maxdeg-tfvis, --nsmooth-tfvis, --no-smoothing, --force-calib |
--no-pipeline-diameters |
off | ignore the calibrator diameters GRAVITY's pipeline recorded in *_CAL_VIS products (used unless --cal names the calibrator) |
--diameter DIAM[±ERR] |
point source | default diameter (and error, mas) of every calibrator; a value given with --cal overrides it for that calibrator |
Caveats
- The in-field calibrator must be unresolved. In dual-field mode the fringe-tracking star calibrates the target only if it is a point source, or if its diameter is given with
--cal. - Off-axis targets lose coherence that the on-axis star does not. Anisoplanatism, off-centre injection and field-dependent aberrations differ between the fringe-tracking star and a target arcseconds away, so calibrated V² carry a loss that this does not remove (review §7). Closure phases are much less affected. With
--dual-field,index.csvgives each exposure's predicted anisoplanatic V² loss (GRAVITY+ Collaboration 2022, eqs. 4–5, from the seeing and isoplanatic angle in the header), and a warning flags losses above--aniso-warn(2%). It is a size estimate, not a correction: single observations scatter about the model. - By default the added calibration error is treated as independent between channels and exposures, although it is common to all channels of a baseline and to every science exposure of the night interpolated from the same calibrators: N exposures would wrongly average it down by √N.
--tf-modeswrites it instead as one nuisance mode per night and baseline (V², on log |V|) or triangle (closure phases, degrees) in<file>.tf_modes.npz, namedtf-vis2-base28-23@<night>andtf-t3phi-tri28-23-18@<night>, so that the same name in several files is one parameter; only the part independent between channels stays in the errors. The mode's shape across channels is estimated from a handful of calibrators and so is noisy (a coefficient of variation of about 0.16 for V² and 0.25 for closure phases with seven calibrators); a mode spanning hundreds of data would then pin its amplitude to that noise and leave the shape's scatter times the realized error unmodelled, so fitted parameters came out about twofold too precise.--tf-mode-shapetherefore smooths the shape (defaultpoly1) and returns the variance this removes to the per-channel remainder, so each channel's total transfer-function error is unchanged (where the smoothed mode exceeds it, the remainder is clipped at zero, with a warning). Use either--tf-modesor a--template(whose modes also carry the calibration residual), not both. - GRAVITY channels are about twofold oversampled. Bin by 2 or more unless channel correlations are modelled.
- Calibrator diameter errors are nuisance modes, not errors. Give them with
--cal "NAME=0.45±0.03"or--diameter 0.2±0.1(a default for every calibrator, e.g. the fringe-tracking star;--calvalues override it field by field). Each calibrated file then gets a.modes.npzbeside it: the change in V² and T3AMP per 1σ of each calibrator's diameter, one mode per calibrator and night, laid out like the file's tables.index.csvreports the largest, in units of the V² error. virgil's correlated-nuisance machinery (OIData.with_gainsandwith_closure_offsets) is meant to marginalise these. Each mode is written twice.<insname>/<observable>/<calibrator>@<night>is absolute, the change in the calibrated values, built from the data.<insname>/<observable>/rel-<calibrator>@<night>is the same parameter relative to the model: d log|V| per 1σ for V² (and VISAMP) and d ln T3AMP for T3AMP, independent of the science values (diameter_modes(..., relative=True)). Pass therel-V² modes to virgil'sOIData.with_gains(modes=...), which avoids the bias of data-built covariances (design/gravity_calibration_review.md, footgun 11). Use one form or the other, never both. - V², T3AMP and T3PHI are calibrated;
OI_VISonly with--vis. VISAMP must be a visibility (AMPTYPabsolute), not a correlated flux. InOI_T3andOI_VISthe singleFLAGcolumn follows the phase, and an amplitude flagged on its own is written as NaN. virgil fits V² and T3PHI when they are present.
Calibration templates
The calibrator PCA (virgil-vlti-calpca; design/gravity_calibrator_pca.md) measures the systematics that calibration leaves behind. A template packages them for one instrument configuration, as nuisance modes in the form virgil's correlated calibration nuisances (with_gains, with_closure_offsets) are designed to take:
- V²: per-telescope and per-baseline gains on log |V|, with chromatic shapes;
- closure phases: optional per-triangle offsets.
virgil-vlti-calibrate --template NAME writes the modes beside each calibrated file (<file>.template_modes.npz, laid out like its tables, one mode per frame and telescope, baseline or triangle). It refuses data in another configuration (field, resolution, polarisation; --any-template-config only warns) and warns about channels outside the template's wavelengths.
Shipped:
| Template | Configuration | V² components | Closure-phase components |
|---|---|---|---|
gravity_single_medium_combined_2023-2025 |
GRAVITY single field, MEDIUM, combined polarisation, 2023–25, mostly ATs, 2.00–2.45 µm | 4: 1.9%, 0.52%, 0.43%, 0.29% per channel in V², 41–81% telescope-based | 2: 0.33°, 0.25° per channel |
gravity_single_high_combined_2023-2025 |
GRAVITY single field, HIGH, combined polarisation, 2023–25, ATs only | 3 | 2: 0.37°, 0.17° per channel |
gravity_single_medium_combined_2023-2025_nowr |
as the first, rebuilt on a smaller calibrator set (71 files, 25 nights); prefer the first for general use | 4 | 2: 0.29°, 0.22° per channel |
Build others from a virgil-vlti-calpca run with virgil-vlti-template residuals.npz --name … --k-vis2 … --k-t3phi … --config ….
Dual-field targets
The dual-field recipe was first written for GRAVITY observations where the science fibre alternates between the target and a companion that carries the fringe tracker.
Development
uv venv --python 3.11 .venv
uv pip install --python .venv/bin/python -e ".[dev]"
.venv/bin/python -m pytest
The tests build synthetic GRAVITY-layout products with a known truth (tests/synthetic.py).
The documentation is built with Zensical, as virgil's is. Its home page is this README, and its tutorials are the executed notebooks in notebooks/ (simulated GRAVITY data from virgil_vlti.synthetic), executed on a cluster rather than a laptop. scripts/sync_docs.py copies both into docs/ and links the design notes there:
uv pip install --python .venv/bin/python -e ".[docs]"
.venv/bin/python scripts/sync_docs.py
.venv/bin/zensical build --clean --strict
Metadata
Release files for virgil-vlti 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| virgil_vlti-0.1.0.tar.gz | 311.5 kB | With provenance | Details for virgil_vlti-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| virgil_vlti-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for virgil_vlti-0.1.0-py3-none-any.whl |
Total release size: 570.7 kB
Release files / virgil_vlti-0.1.0.tar.gz
| Download URL | virgil_vlti-0.1.0.tar.gz |
|---|---|
| Size | 311.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
777dde2d7b996decd7cabfb81d0561cf8a625bc437a70ed5687d229d7701c923
|
|
BLAKE2b-256 checksum How to use checksums |
de80508d67d5335842402bec220ac61437e54e7dc5c92cbf732d1c9b23c7b3ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / virgil_vlti-0.1.0-py3-none-any.whl
| Download URL | virgil_vlti-0.1.0-py3-none-any.whl |
|---|---|
| Size | 259.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
375f2b2827cb4ebb16c8baabc9d3bcedf5435e8e8adb3db13cc220956f0285ae
|
|
BLAKE2b-256 checksum How to use checksums |
c3a13304219abe302288c3b7e765d7a9ddeada3ae6bb956ab64c353f49b083cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for virgil-vlti 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for virgil_vlti-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.