Wenuray
Self-calibration for radio interferometers, written once and run anywhere.
Wenuray lets you describe a self-calibration procedure as a short recipe (a dozen lines of TOML or a few Python objects) and runs it on any measurement set, from any array. It images with CASA tclean, Pyralysis or your own imager, solves for phases and then amplitudes round after round, and keeps a round only if it really improves the image. Every run records what it did, so you or a referee can repeat it exactly.
Why self-calibration?
An interferometer measures visibilities, and every antenna i multiplies what it sees by its own complex gain gᵢ(t, ν):
V_ij(observed) = g_i · g_j* · V_ij(true) + noise
Calibrating against a calibrator source removes most of those gains, but not all of them. The atmosphere and the ionosphere change faster than the calibrator cycle, and the calibrator is in another part of the sky. What's left over shows up as stripes, sidelobes and a noise floor far above the thermal limit.
Self-calibration uses the target itself. An array of N antennas has N(N−1)/2 baselines but only N unknown gains, so even an imperfect model of the sky constrains the gains well. The loop is:
- Image the data and keep the model.
- Solve for the antenna gains that best match the data to that model (
gaincal). - Apply them and image again. If the image is better, keep the solution and go on with a shorter interval or with amplitudes; if not, roll it back.
Why Wenuray?
In practice, most astronomers write a new self-calibration script for each dataset: a new loop,
the same choices made again (solution intervals, reference antennas, which tables to pre-apply
with which spwmap, when to stop), and the same bookkeeping mistakes waiting to happen. These
scripts are hard to share, hard to review and hard to reproduce. They are also usually tied to
CASA, and so to tclean, the de facto standard imaging algorithm, even when another
reconstruction method would suit the data better.
Wenuray continues snow, and replaces those scripts with a framework:
- Describe the procedure, not the loop. A self-calibration run is a short recipe. Wenuray owns
the loop and checks the recipe against self-calibration theory before it touches your data. For
example, it refuses an amplitude round with no phase solution before it, and it warns when a
solution interval is too short to reach your
minsnr. - Use any imager, not only tclean. The imager is a plug-in: tclean, Pyralysis (optimization-based reconstruction) or your own algorithm, all in the same loop.
- Extend it without forking it. Imagers, acceptance rules and observers are classes you can subclass; imagers and acceptance rules of your own can be named in a recipe, so a new idea doesn't need a new script.
Installation
pip install wenuray
You need Python 3.11, 3.12 or 3.13 on Linux or macOS, the platforms CASA ships wheels for. Conda, Docker, source installs and the optional extras (Pyralysis, plots) are in the installation guide.
Debian, Ubuntu or Arch? The casatools wheel pip picks by default fails to import there (
OPENSSL_3.0.1 not found). From a clone,pip install -r requirements.txt .pins a wheel that works.
Quick start
Your data should already be calibrated, and the target split out so that the calibrated
visibilities sit in DATA:
split(vis="calibrated.ms", outputvis="target.ms", field="my_target", datacolumn="corrected")
Your first recipe
Phase, then amplitude-phase self-calibration of an ALMA dataset. The values (cell size, threshold, reference antenna) belong to this dataset; adapt them to yours.
ms = "target.ms" # paths are relative to the recipe
output = "target_selfcal"
[solve]
refant = "DA51"
minblperant = 6
[imager]
type = "tclean"
niter = 100
threshold = "0.1mJy"
deconvolver = "hogbom"
usemask = "auto-multithresh"
geometry = { imsize = 1024, cell = "0.005arcsec" }
weighting = { weighting = "briggs", robust = 0.5 }
[[steps]]
type = "phase" # Phasecal; "amp" is Ampcal, "ampphase" AmpPhasecal
solint = "inf"
solve = { combine = "spw" }
[[steps]]
type = "phase"
solint = "600s"
solve = { combine = "spw" }
[[steps]]
type = "ampphase"
solint = "inf"
imaging = { threshold = "0.025mJy" }
Three rounds: a phase solution over the whole scan, combining spectral windows for S/N; a shorter one, now that the model is better; and finally amplitudes and phases, cleaned deeper. Check it, then run it:
wenuray validate recipe.toml # builds the run and checks it, without touching the data
wenuray run recipe.toml # images, solves, applies and decides, round by round
wenuray init > recipe.toml writes a generic recipe to start from, and
wenuray run recipe.toml --ms other.ms runs the same procedure on another dataset.
The same run in Python
Each table in a recipe is a constructor and its keys are that class's arguments, so a recipe and a
script build equal objects. This is
examples/alma/selfcal.py;
a test keeps the two identical.
"""
Phase, then amplitude-phase self-calibration of an ALMA dataset imaged with tclean.
Usage: python selfcal.py <visfile> <output_directory> [--plot]
"""
import argparse
import logging
from casatasks import split
from wenuray.config import ImageGeometry, SolveOptions, Weighting
from wenuray.imaging import Tclean
from wenuray.selfcalibration import AmpPhasecal, GainPlotObserver, LoggingObserver, Phasecal, Selfcal
def parse_arguments():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("visfile", help="Measurement set with the calibrated data in DATA")
parser.add_argument("output", help="Directory for the images, tables and the result record")
parser.add_argument("--plot", action="store_true", help="Plot the gain solutions")
return parser.parse_args()
if __name__ == '__main__':
args = parse_arguments()
# Wenuray logs each self-calibration round's metrics at INFO
logging.basicConfig(level=logging.INFO)
# tclean's own parameter names. Automasking values for long baselines:
# https://casaguides.nrao.edu/index.php/Automasking_Guide
imager = Tclean(
ImageGeometry(imsize=1024, cell="0.005arcsec"),
Weighting("briggs", robust=0.5),
niter=100,
threshold="0.1mJy",
specmode="mfs",
deconvolver="hogbom",
gridder="standard",
usemask="auto-multithresh",
sidelobethreshold=3.0,
noisethreshold=5.0,
minbeamfrac=0.3,
lownoisethreshold=1.5,
negativethreshold=0.0,
interactive=False # True to edit the masks by hand
)
# Phases combined over spectral windows, then amplitudes and phases per spectral window,
# cleaned deeper. Each spwmap is derived from combine.
steps = [
Phasecal("inf", solve={"combine": "spw"}),
Phasecal("600s", solve={"combine": "spw"}),
AmpPhasecal("inf", imaging={"threshold": "0.025mJy"}),
]
selfcal = Selfcal(
args.visfile,
imager,
steps,
solve=SolveOptions(refant="DA51", minblperant=6, minsnr=3.0, gaintype="T"),
output=args.output,
)
observers = [LoggingObserver()] + ([GainPlotObserver()] if args.plot else [])
result = selfcal.run(observers)
# The self-calibrated data, for final imaging
split(vis=result.ms, outputvis=args.output + "/selfcal.ms", datacolumn="corrected")
Selfcal.from_toml("recipe.toml") gives you the same Selfcal from the recipe, to inspect,
change or run; selfcal.to_toml(...) writes it back.
What you get
- A decision for every round. By default a round is kept only if the peak S/N of the image
rises, every solution interval keeps enough antennas, and amplitude rounds don't lose model
flux: PSNR alone can rise while the model goes wrong. A rejected round is rolled back and the
run stops. You can combine your own acceptance rules with
AllOf/AnyOf. - Checks before any I/O. An invalid schedule raises
ScheduleErrorwhile the run is being built. Before eachgaincal, Wenuray predicts the solution's S/N from the data and tells you the shortest solint that would reachminsnr. - Bookkeeping you don't have to do. Every accepted table stays applied in later solves, each
with its own
spwmapandinterp, and oneSelectionis used by the solve, the apply and the imager. - Reproducibility. Each output directory holds the resolved
recipe.toml, with every default written out, and aselfcal.tomlwith each round's metrics, the accepted tables and the provenance: a fingerprint of the input measurement set and the versions of Python, CASA, Wenuray and the imager. - Any imager. CASA tclean, Pyralysis (optimization-based
reconstruction), or your own: subclass
Imageror wrap a function. Each one imagesCORRECTED_DATAand writesMODEL_DATA, and Wenuray checks that it does. - Any array. The defaults come from theory and from the data, not from an instrument: there are examples for ALMA, the VLA and e-MERLIN.
Learn more
- How a run works: preparing the data, schedules, acceptance, recipes and presets, resuming, and an API overview.
- Imagers: the imager contract, the Nyquist-cell check, tclean, Pyralysis and writing your own.
- Examples: ALMA, VLA and e-MERLIN, each as a recipe and the script that builds the same run.
- Migrating from snow: code written for snow, the name before Wenuray 1.0, raises an error that shows the new call.
- API reference on Read the Docs.
About the name
Wenuray (also written Huenuray) is a Mapuche woman's name. In Mapudungun it means "flower of the sky": wenu, the sky, and rayen, flower. The radio sky is what Wenuray brings into focus, and its roots are in Chile, home of many of the world's radio telescopes.
Wenuray is dedicated to the women of radio astronomy. Each major release carries one of their names, beginning with v1.0 "Payne-Scott", after Ruby Payne-Scott (1912–1981), the first woman radio astronomer and a pioneer of radio interferometry.
Wenuray was called snow (caSa pythoN self-calibratiOn frameWork) before version 1.0.
Citing Wenuray
If Wenuray helps your research, please cite it. GitHub's Cite this repository button (from
CITATION.cff) gives
APA and BibTeX, or use the BibTeX below. The DOI, 10.5281/zenodo.23775089, always
resolves to the latest release; Zenodo gives each version its own DOI too.
@software{wenuray,
author = {Cárcamo, Miguel and Casassus, Simon},
title = {Wenuray: radio-interferometric self-calibration},
url = {https://github.com/miguelcarcamov/wenuray},
doi = {10.5281/zenodo.23775089},
version = {<version>},
year = {<year>}
}
Contributing and support
Bug reports, questions and pull requests are welcome on
GitHub; see
CONTRIBUTING.md.
For CASA itself, the CASA documentation is the place to look.
You can also write to miguel.carcamo@usach.cl.
Wenuray is free software under the GNU General Public License v3 or later. It is built on CASA (Common Astronomy Software Applications), developed by the National Radio Astronomy Observatory (NRAO).
Metadata
Release files for wenuray 1.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 |
|---|---|---|---|---|
| wenuray-1.1.0.tar.gz | 180.3 kB | With provenance | Details for wenuray-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wenuray-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for wenuray-1.1.0-py3-none-any.whl |
Total release size: 266.7 kB
Release files / wenuray-1.1.0.tar.gz
| Download URL | wenuray-1.1.0.tar.gz |
|---|---|
| Size | 180.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d92c3d62ed56f7e6405b1f6f5180e5ac87c3b57698da87e553d09134728bb8ae
|
|
BLAKE2b-256 checksum How to use checksums |
c5e9e87951df4bf03205be8ab4c45ee3a15a19c5dc2d921d52b0e2763f783d09
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 29, 2026.
Transparency logRelease files / wenuray-1.1.0-py3-none-any.whl
| Download URL | wenuray-1.1.0-py3-none-any.whl |
|---|---|
| Size | 86.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f668cf923e3a6531868478019d826c58e03d88fcf19dd4fb0ff00380b9f1094f
|
|
BLAKE2b-256 checksum How to use checksums |
3ba33a0668fb586be18ba7152319e997cc4d8a05cccca307ead89e7b998622e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 29, 2026.
Transparency logSecurity for wenuray 1.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 wenuray-1.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 29, 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.