Skip to main content

Logo

Tests

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)

Source distribution for esc-etc 0.1.0
File Size Uploaded
esc_etc-0.1.0.tar.gz 929.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for esc-etc 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page