Metadata-Version: 2.4
Name: esc_etc
Version: 0.1.0
Summary: Exposure time calculator for the ExtraSolar Camera (ESC) on Lazuli
Author-email: Aaron Goldtooth <gold1992@arizona.edu>, Justin Hom <jrhom@arizona.edu>, Jess Johnson <jajohnson@arizona.edu>, Sanchit Sabhlok <ssabhlok@arizona.edu>, Gudmundur Stefansson <gstefansson@schmidtsciences.org>
Maintainer-email: Gudmundur Stefansson <gstefansson@schmidtsciences.org>
License-Expression: BSD-3-Clause
Project-URL: Homepage, https://github.com/schmidt-observatories/esc-etc/blob/main/README.md
Project-URL: Changelog, https://github.com/schmidt-observatories/esc-etc/blob/main/docs/sphinx/changelog.rst
Project-URL: Repository, https://github.com/schmidt-observatories/esc-etc
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: COPYRIGHT
Requires-Dist: synphot
Requires-Dist: scipy
Requires-Dist: numpy
Requires-Dist: astropy
Requires-Dist: matplotlib
Requires-Dist: pandas
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=7; extra == "docs"
Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
Requires-Dist: nbsphinx>=0.9; extra == "docs"
Requires-Dist: ipykernel; extra == "docs"
Requires-Dist: myst-parser; extra == "docs"
Dynamic: license-file

![Logo](docs/sphinx/_static/logo_schmidt_observatory_system.png)

[![Tests](https://github.com/schmidt-observatories/esc-etc/actions/workflows/tests.yml/badge.svg)](https://github.com/schmidt-observatories/esc-etc/actions/workflows/tests.yml)

# 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](https://github.com/uasal/stp_etc_esc)), with updates from Justin Hom and contributions from Sanchit Sabhlok and Jess Johnson.

# Installation

## **1. Clone the Repository**
```sh
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):
```sh
pip install -e .
```

With optional extras:
```sh
pip install -e ".[dev]"    # development tools (pytest, ruff, build, twine)
pip install -e ".[docs]"   # documentation build (Sphinx, nbsphinx, etc.)
```

> [!Important]
> Use an **editable** install (`pip install -e .`). A plain `pip install .` copies the package into `site-packages` and can shadow the repository's `src/` tree, reintroducing import and version surprises.

Once released the package also installs from PyPI with `pip install esc_etc`. To confirm the installation:
```python
import esc_etc
esc_etc.__version__
```

# Quick Start

```python
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](https://github.com/uasal/config_um) and [config_um_esc](https://github.com/uasal/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:

```sh
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`](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:

```sh
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/`:

```bash
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.
