{ "cells": [ { "cell_type": "markdown", "id": "1f7646f6", "metadata": {}, "source": [ "# Getting started with the WCC ETC\n", "\n", "The Widefield Context Camera (WCC) Exposure Time Calculator (ETC) estimates the\n", "signal-to-noise ratio (SNR) a source will reach in a given exposure. The workflow is\n", "always the same three steps:\n", "\n", "1. **Build a scene** — the astrophysical source plus its background (`get_scene`).\n", "2. **Build a simulation** for a sensor + filter combination\n", " (`Simulation.from_sensor_and_scene`).\n", "3. **Ask it questions** — SNR for a time, required time for an SNR, peak pixel /\n", " saturation, and more.\n", "\n", "This notebook walks through that core loop. The other notebooks in this directory go\n", "deeper on individual features (PSF-aware SNR, defocus, `n_reads`, parametric spectra,\n", "saturation) — see **Where to go next** at the end.\n" ] }, { "cell_type": "markdown", "id": "da03953f", "metadata": {}, "source": [ "## Imports\n", "\n", "We set the matplotlib fonts to match the rest of the notebooks." ] }, { "cell_type": "code", "execution_count": null, "id": "fc217194", "metadata": { "execution": { "iopub.execute_input": "2026-06-06T21:06:12.877939Z", "iopub.status.busy": "2026-06-06T21:06:12.877753Z", "iopub.status.idle": "2026-06-06T21:06:16.486563Z", "shell.execute_reply": "2026-06-06T21:06:16.486118Z" } }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "import wcc_etc\n", "\n", "wcc_etc.set_wcc_style()" ] }, { "cell_type": "markdown", "id": "9c008528", "metadata": {}, "source": [ "## 1. Build a scene\n", "\n", "`get_scene` returns the thing you want to observe together with its background. Here we use\n", "a Sun-like **G5V** star at **r = 20 mag** on a **zodiacal** background (itself specified by a\n", "bandpass and a surface-brightness magnitude). `host=None` means there is no host galaxy /\n", "extended component.\n", "\n", "The `name` can be a spectral type (`'G5V'`, `'K3IV'`, ...) or a parametric spectrum\n", "(`'blackbody'`, `'flat'`, `'powerlaw'`, `'emission'`) — see `06_source_spectra.ipynb`." ] }, { "cell_type": "code", "execution_count": null, "id": "e9d88fb7", "metadata": { "execution": { "iopub.execute_input": "2026-06-06T21:06:16.488681Z", "iopub.status.busy": "2026-06-06T21:06:16.488506Z", "iopub.status.idle": "2026-06-06T21:06:16.703460Z", "shell.execute_reply": "2026-06-06T21:06:16.703236Z" } }, "outputs": [], "source": [ "scene = wcc_etc.get_scene(\n", " name=\"G5V\",\n", " mag=20,\n", " host=None,\n", " background=\"zodi\",\n", " bandpass=\"johnson_r\",\n", " background_prop={\"bandpass\": \"johnson_r\", \"mag\": 22.5},\n", ")\n", "\n", "print(\"source :\", scene.source.meta[\"spectrum\"], \"at r =\", scene.source.mag)\n", "print(\"background : zodi, johnson_r =\", scene.background.mag, \"/ arcsec^2\")" ] }, { "cell_type": "markdown", "id": "62ab36b3", "metadata": {}, "source": [ "## 2. Build a simulation for a sensor + filter\n", "\n", "`Simulation.from_sensor_and_scene` pairs the scene with a detector + filter, given as a\n", "`kind:band` label (e.g. `'sony:r'`, `'sony:bb'`, `'qcmos:r'`). The simulation reads the\n", "sensor's pixel size, gain, read noise, dark current, and the filter throughput from the\n", "bundled config files." ] }, { "cell_type": "code", "execution_count": null, "id": "596492d3", "metadata": { "execution": { "iopub.execute_input": "2026-06-06T21:06:16.704758Z", "iopub.status.busy": "2026-06-06T21:06:16.704619Z", "iopub.status.idle": "2026-06-06T21:06:16.765226Z", "shell.execute_reply": "2026-06-06T21:06:16.765001Z" } }, "outputs": [], "source": [ "sensor_and_filter = \"sony:r\"\n", "simu = wcc_etc.Simulation.from_sensor_and_scene(sensor_and_filter, scene)\n", "\n", "print(\"filter :\", sensor_and_filter)\n", "print(\"pixel size :\", simu.meta[\"sensor\"][\"pixel_size\"])\n", "print(\"bit depth :\", simu.meta[\"sensor\"][\"bit_depth\"], \"bit\")" ] }, { "cell_type": "markdown", "id": "a9a35ed0", "metadata": {}, "source": [ "## 3. Compute the SNR\n", "\n", "`get_snr(time)` runs the 2-D image simulation and returns a dict; `[\"snr\"]` is the\n", "signal-to-noise ratio. The time can be a scalar or an array — it broadcasts — so a\n", "single call gives you an SNR-vs-time curve." ] }, { "cell_type": "code", "execution_count": null, "id": "5335e8ea", "metadata": { "execution": { "iopub.execute_input": "2026-06-06T21:06:16.766541Z", "iopub.status.busy": "2026-06-06T21:06:16.766460Z", "iopub.status.idle": "2026-06-06T21:06:17.251838Z", "shell.execute_reply": "2026-06-06T21:06:17.251601Z" } }, "outputs": [], "source": [ "# single exposure time\n", "snr_60 = simu.get_snr(time=60)[\"snr\"]\n", "print(f\"SNR at 60 s : {float(snr_60):.1f}\")\n", "\n", "# an array of times broadcasts\n", "times = np.linspace(1, 300, 100)\n", "snr_curve = simu.get_snr(time=times)[\"snr\"]\n", "\n", "fig, ax = plt.subplots(figsize=(6.5, 4))\n", "ax.plot(times, snr_curve, lw=2)\n", "ax.axvline(60, ls=\"--\", color=\"0.6\")\n", "ax.set_xlabel(\"exposure time [s]\")\n", "ax.set_ylabel(\"SNR\")\n", "ax.set_title(\"SNR vs exposure time (G5V, r = 20, sony:r)\")\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "3261e260", "metadata": {}, "source": [ "## 4. Inspect and update parameters\n", "\n", "Every tunable parameter is listed in `simu.mutable_parameters`, and the current values live\n", "in `simu.meta`. Use `simu.update(**kwargs)` to change them in place (double-underscore picks\n", "out nested fields, e.g. `source__mag`), then recompute." ] }, { "cell_type": "code", "execution_count": null, "id": "f81d4f03", "metadata": { "execution": { "iopub.execute_input": "2026-06-06T21:06:17.253128Z", "iopub.status.busy": "2026-06-06T21:06:17.253032Z", "iopub.status.idle": "2026-06-06T21:06:17.560537Z", "shell.execute_reply": "2026-06-06T21:06:17.560325Z" } }, "outputs": [], "source": [ "print(\"some mutable parameters:\")\n", "print(\" \", simu.mutable_parameters[:10], \"...\")\n", "\n", "snr_before = float(simu.get_snr(60)[\"snr\"])\n", "\n", "# make the source 2 mag brighter and add some pointing jitter\n", "simu.update(source__mag=18, jitter_sigma=10)\n", "\n", "snr_after = float(simu.get_snr(60)[\"snr\"])\n", "print(f\"\\nSNR @60 s before (r=20) : {snr_before:.1f}\")\n", "print(f\"SNR @60 s after (r=18, jitter) : {snr_after:.1f}\")\n", "assert snr_after > snr_before # brighter source -> higher SNR" ] }, { "cell_type": "markdown", "id": "639cff56", "metadata": {}, "source": [ "## 5. Visualize the source spectrum and filter bandpass\n", "\n", "The scene and sensor objects know how to plot themselves, which is handy for a quick sanity\n", "check that you set up the source and filter you intended." ] }, { "cell_type": "code", "execution_count": null, "id": "53c2d576", "metadata": { "execution": { "iopub.execute_input": "2026-06-06T21:06:17.561983Z", "iopub.status.busy": "2026-06-06T21:06:17.561889Z", "iopub.status.idle": "2026-06-06T21:06:17.693287Z", "shell.execute_reply": "2026-06-06T21:06:17.693046Z" } }, "outputs": [], "source": [ "fig, axes = plt.subplots(1, 2, figsize=(12, 4.2))\n", "\n", "# source spectrum (the SceneElement plots itself onto a given axes)\n", "simu.scene.source.show(ax=axes[0])\n", "axes[0].set_title(\"source spectrum (G5V, r = 18)\")\n", "\n", "# filter bandpass / throughput (synphot SpectralElement -> plot its arrays)\n", "bp = simu.sensor.bandpass\n", "wave = bp.waveset\n", "axes[1].plot(wave.to(\"Angstrom\").value, bp(wave).value, lw=2)\n", "axes[1].set_xlabel(\"Wavelength [A]\")\n", "axes[1].set_ylabel(\"throughput\")\n", "axes[1].set_title(\"filter throughput (sony:r)\")\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "a249586e", "metadata": {}, "source": [ "## Where to go next\n", "\n", "| Notebook | What it adds |\n", "|---|---|\n", "| `02_saturation_flag.ipynb` | Peak-pixel value and saturation flagging (`get_peak_pixel`, `is_saturated`). |\n", "| `03_from_sensorfilter.ipynb` | Build from a canonical `kind:band` label with the PSF auto-selected from the filter focus level. |\n", "| `04_psf_and_image_snr.ipynb` | PSF models (`AiryPSF`/`DefocusPSF`), full image simulation, and PSF-aware aperture SNR. |\n", "| `05_n_reads_exptime.ipynb` | Multiple reads (`n_reads`) and the exposure-time-for-SNR inverses. |\n", "| `06_source_spectra.ipynb` | Parametric source spectra (blackbody / flat / powerlaw / emission). |\n" ] }, { "cell_type": "markdown", "id": "72b87854", "metadata": {}, "source": [ "## Summary\n", "\n", "- **Scene → Simulation → question** is the whole workflow.\n", "- `wcc_etc.get_scene(name=..., mag=..., background=..., ...)` builds the source + background.\n", "- `wcc_etc.Simulation.from_sensor_and_scene('sony:r', scene)` pairs it with a detector + filter.\n", "- `simu.get_snr(time)` returns SNR for a scalar or array of times.\n", "- `simu.mutable_parameters` lists what you can change; `simu.update(**kwargs)` changes it in\n", " place (use `source__mag`-style keys for nested fields).\n", "- Scene and sensor objects plot themselves for quick sanity checks.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.5" } }, "nbformat": 4, "nbformat_minor": 5 }