esc-etc
Exposure Time Calculator (ETC) for the ExtraSolar Camera (ESC), the coronagraphic instrument on Lazuli.
A web-based ETC is available here: https://simulators.schmidtobservatorysystem.org/esc/
Adapted and extended from the exposure time calculator developed by Aaron Goldtooth at the University of Arizona (uasal/stp_etc_esc), with updates from Justin Hom and contributions from Sanchit Sabhlok and Jess Johnson.
Installation
1. Clone the Repository
git clone https://github.com/schmidt-observatories/esc-etc.git
cd esc-etc
2. Install the Package
Core install (Python 3.11 or newer; astropy, numpy, scipy, matplotlib, pandas and synphot are pulled in):
pip install -e .
With optional extras:
pip install -e ".[dev]" # development tools (pytest, ruff, build, twine)
pip install -e ".[docs]" # documentation build (Sphinx, nbsphinx, etc.)
Once released the package also installs from PyPI with pip install esc_etc. To confirm the installation:
import esc_etc
esc_etc.__version__
Quick Start
from esc_etc import Simulation
# A planet 20 mag fainter than its host, at 0.4" separation, behind a
# coronagraph with 1e-8 raw dark-hole contrast and a post-processing gain of 10
sim = Simulation.from_default(hoststarmag=-0.353, planetdeltamag=20,
contrast=1e-8, ppgain=10, separation=0.4)
# SNR after one hour in 100 s frames. get_snr returns a dict; ["snr"] is the
# signal-to-noise ratio. Times are astropy quantities or plain seconds.
snr = sim.get_snr(total_time=3600, frame_time=100)["snr"] # ~9.87
# Invert: whole-frame integration time to reach SNR 5
t = sim.get_int_time_for_snr(5, frame_time=100)["int_time"]
# Frame time at which the brightest pixel in the planet's aperture fills the well
sim.saturation_time()
# Change any parameter (child__param syntax), recompute, put it back
sim.update(coronagraph__ppgain=20)
snr_new = sim.get_snr(3600, 100)["snr"]
sim.reset()
# One of the ESC filters as the bandpass, at end of life or beginning of life
sim = Simulation.from_default(filter="F630N", lifetime="eol")
# Plots: every one is a method, takes ax=, and returns (fig, ax)
fig, axes = sim.plot() # 2 x 3 overview
fig, ax = sim.plot_snr_vs_time(sweep={"coronagraph__raw_dh_contrast": [1e-7, 1e-8, 1e-9]})
fig, ax = sim.plot_count_rates()
fig, ax = sim.sensor.plot_bandpass(ax=ax) # draw onto an axis you already have
fig, ax = sim.coronagraph.plot_offaxis_throughput()
Top-level exports: Simulation, Telescope, Sensor, Scene, SceneElement, get_scene, Coronagraph, AnalyticCoronagraphNoiseModel, CountRates, FILTERS, the plot_* functions in esc_etc.plotting (the same plots for raw arrays) and use_lazuli_style() for the packaged lazuli.mplstyle. esc_calc_snr and Observatory are deprecated compatibility wrappers.
Tutorial
See the notebooks/ directory for example tutorials, also rendered in the documentation site:
| Notebook | What it covers |
|---|---|
01_getting_started.ipynb |
SNR for a given time, time for a target SNR, the count-rate budget and the sim.plot() overview. |
02_components_and_parameters.ipynb |
The four components, the photometric aperture choice, the ESC filter set with EOL/BOL curves, from_components, update() and reset(). |
03_coronagraph_and_contrast.ipynb |
What contrast, post-processing gain and separation do to the SNR curve, drawn with plot_snr_vs_time(sweep=...), plus reference subtraction. |
04_integration_time_snr_saturation.ipynb |
Frame time and read noise, inverting for integration time, and the saturation check with its caveat. |
The notebooks are generated by scripts/build_tutorials.py and executed with jupyter nbconvert --execute; edit the script, not the JSON.
Known limitations
Also on the documentation site, with the issue links. In short:
- The PSF behind the photometric aperture, encircled energy and peak-pixel model is an Airy pattern for the full 3 m pupil: no Lyot-stop or pointing-jitter term, and the focal-plane-mask throughput table definition is still open (#30).
- The raw contrast is a single scalar across the dark hole, with no inner/outer working angle guard, and the off-axis throughput is a nearest-neighbour lookup (#32).
saturation_time()only models the planet location at the dark-hole contrast, so for a bright host the detector-safe frame time can be much shorter (#33).- Not yet validated against as-measured detector, vortex or on-sky data (#34).
Notes
Instrument and telescope properties (mirrors, coatings, filters, detector tables) come from configuration files for the Lazuli 3 m telescope and the ESC instrument, bundled under src/esc_etc/data/lazuli/ (telescope/ and esc/), so a plain pip install needs no git access. They are snapshots of the UASAL repositories config_um and config_um_esc ("UM" is the UA code name for Lazuli), linked here for reference: those repositories are maintained by UASAL and are not edited in this package. scripts/sync_uasal_configs.py pulls them into the snapshot, and SNAPSHOT.txt records which revision each came from. Maintainers refresh the snapshot with:
pip install -r scripts/requirements-uasal.txt
python scripts/sync_uasal_configs.py
What ships in the wheel and where each file comes from is listed in DATA_SOURCES.md.
Your own configuration can be supplied through the from_config class methods, as long as it follows the same layout as the bundled snapshot.
Tests
pip install -e ".[dev]" then pytest. On every pull request .github/workflows/tests.yml runs the suite, ruff check --select F and ruff format --check on src and tests, and builds the sdist and wheel, checks them with twine, audits their contents and smoke-tests the installed wheel with the Quick Start above.
Releases
The version comes from the git tag (setuptools-scm). To release: move the Unreleased section of docs/sphinx/changelog.rst under a version heading, merge, then tag the merge commit on main and push the tag:
git tag v0.1.0rc1 && git push origin v0.1.0rc1 # rc tags publish to TestPyPI
git tag v0.1.0 && git push origin v0.1.0 # plain tags publish to PyPI
.github/workflows/publish.yml builds, audits and smoke-tests the wheel, checks that the tag matches the built version, and publishes through PyPI trusted publishing (no tokens; the pypi and testpypi environments must exist on PyPI and in this repository).
Documentation
The full documentation site (installation, a user guide, rendered tutorial
notebooks, and an auto-generated API reference) is built with Sphinx and
the Read the Docs theme. It lives under docs/sphinx/:
pip install -e ".[docs]" # Sphinx + theme + nbsphinx (+ the package itself)
brew install pandoc # or: conda install pandoc / apt-get install pandoc
cd docs/sphinx
make html # output in _build/html/index.html
The tutorial notebooks render from their saved outputs, so nothing is executed at build time. Read the Docs builds and publishes the site from .readthedocs.yaml on every push to main and as a preview on every pull request, with warnings treated as errors.
Metadata
Release files for esc-etc 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 | |
|---|---|---|---|
| esc_etc-0.1.0.tar.gz | 929.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| esc_etc-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / esc_etc-0.1.0.tar.gz
| Download URL | esc_etc-0.1.0.tar.gz |
|---|---|
| Size | 929.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7cba965f54955e98f8f667b02ac9fe317376a75697841792de527190bc1ec778
|
|
BLAKE2b-256 checksum How to use checksums |
4087a08ebf6f3747bd1a4b9d69a4e4959dd0c56f10002863a7564bcab68b8b11
|
| 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 13, 2026.
Transparency logRelease files / esc_etc-0.1.0-py3-none-any.whl
| Download URL | esc_etc-0.1.0-py3-none-any.whl |
|---|---|
| Size | 890.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
53b5b312e88a5dfe2f0bb74767a32e42fbb2adf956601963bedc5d0cf2e39111
|
|
BLAKE2b-256 checksum How to use checksums |
d1d2bdad65c570eefe8a6e52b6e2bb9e28cdab3d13f91f30c13cf9d25531d42f
|
| 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 13, 2026.
Transparency log