wcc-sim 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wcc_sim-0.1.0/LICENSE +28 -0
- wcc_sim-0.1.0/PKG-INFO +220 -0
- wcc_sim-0.1.0/README.md +188 -0
- wcc_sim-0.1.0/pyproject.toml +65 -0
- wcc_sim-0.1.0/setup.cfg +4 -0
- wcc_sim-0.1.0/src/wcc_phot/__init__.py +6 -0
- wcc_sim-0.1.0/src/wcc_phot/apphot.py +69 -0
- wcc_sim-0.1.0/src/wcc_phot/centroid.py +41 -0
- wcc_sim-0.1.0/src/wcc_phot/cli.py +110 -0
- wcc_sim-0.1.0/src/wcc_phot/flags.py +7 -0
- wcc_sim-0.1.0/src/wcc_phot/geometry.py +93 -0
- wcc_sim-0.1.0/src/wcc_phot/io.py +110 -0
- wcc_sim-0.1.0/src/wcc_phot/lightcurve.py +103 -0
- wcc_sim-0.1.0/src/wcc_phot/live.py +141 -0
- wcc_sim-0.1.0/src/wcc_phot/pipeline.py +315 -0
- wcc_sim-0.1.0/src/wcc_phot/psfphot.py +78 -0
- wcc_sim-0.1.0/src/wcc_phot/report.py +454 -0
- wcc_sim-0.1.0/src/wcc_phot/select.py +83 -0
- wcc_sim-0.1.0/src/wcc_phot/validation.py +243 -0
- wcc_sim-0.1.0/src/wcc_sim/__init__.py +29 -0
- wcc_sim-0.1.0/src/wcc_sim/astrometry.py +136 -0
- wcc_sim-0.1.0/src/wcc_sim/brightcat.py +292 -0
- wcc_sim-0.1.0/src/wcc_sim/catalog.py +133 -0
- wcc_sim-0.1.0/src/wcc_sim/chromatic.py +179 -0
- wcc_sim-0.1.0/src/wcc_sim/cli.py +92 -0
- wcc_sim-0.1.0/src/wcc_sim/data/gaia_dr3_bp.dat +426 -0
- wcc_sim-0.1.0/src/wcc_sim/data/gaia_dr3_g.dat +731 -0
- wcc_sim-0.1.0/src/wcc_sim/data/gaia_dr3_rp.dat +471 -0
- wcc_sim-0.1.0/src/wcc_sim/data/plot_default.mplstyle +38 -0
- wcc_sim-0.1.0/src/wcc_sim/data/psfs/lazuli_scatter_450nm_profile.ecsv +237 -0
- wcc_sim-0.1.0/src/wcc_sim/data/spt_synthetic_colors.csv +28 -0
- wcc_sim-0.1.0/src/wcc_sim/detectors.py +82 -0
- wcc_sim-0.1.0/src/wcc_sim/extended.py +178 -0
- wcc_sim-0.1.0/src/wcc_sim/fitswriter.py +102 -0
- wcc_sim-0.1.0/src/wcc_sim/pipeline.py +514 -0
- wcc_sim-0.1.0/src/wcc_sim/plotting.py +283 -0
- wcc_sim-0.1.0/src/wcc_sim/psf.py +95 -0
- wcc_sim-0.1.0/src/wcc_sim/psfreport.py +719 -0
- wcc_sim-0.1.0/src/wcc_sim/render.py +208 -0
- wcc_sim-0.1.0/src/wcc_sim/scatter.py +422 -0
- wcc_sim-0.1.0/src/wcc_sim/starflux.py +289 -0
- wcc_sim-0.1.0/src/wcc_sim/wcsutil.py +24 -0
- wcc_sim-0.1.0/src/wcc_sim/wings.py +157 -0
- wcc_sim-0.1.0/src/wcc_sim.egg-info/PKG-INFO +220 -0
- wcc_sim-0.1.0/src/wcc_sim.egg-info/SOURCES.txt +73 -0
- wcc_sim-0.1.0/src/wcc_sim.egg-info/dependency_links.txt +1 -0
- wcc_sim-0.1.0/src/wcc_sim.egg-info/entry_points.txt +3 -0
- wcc_sim-0.1.0/src/wcc_sim.egg-info/requires.txt +20 -0
- wcc_sim-0.1.0/src/wcc_sim.egg-info/top_level.txt +2 -0
- wcc_sim-0.1.0/tests/test_astrometry.py +226 -0
- wcc_sim-0.1.0/tests/test_brightcat.py +519 -0
- wcc_sim-0.1.0/tests/test_catalog.py +154 -0
- wcc_sim-0.1.0/tests/test_chromatic.py +138 -0
- wcc_sim-0.1.0/tests/test_cli.py +54 -0
- wcc_sim-0.1.0/tests/test_detectors.py +71 -0
- wcc_sim-0.1.0/tests/test_edge_cases.py +131 -0
- wcc_sim-0.1.0/tests/test_etc_crosscheck.py +108 -0
- wcc_sim-0.1.0/tests/test_extended.py +218 -0
- wcc_sim-0.1.0/tests/test_fitswriter.py +63 -0
- wcc_sim-0.1.0/tests/test_phot_io.py +100 -0
- wcc_sim-0.1.0/tests/test_phot_lightcurve.py +87 -0
- wcc_sim-0.1.0/tests/test_phot_live.py +67 -0
- wcc_sim-0.1.0/tests/test_phot_measure.py +103 -0
- wcc_sim-0.1.0/tests/test_phot_pipeline.py +108 -0
- wcc_sim-0.1.0/tests/test_phot_report.py +70 -0
- wcc_sim-0.1.0/tests/test_phot_select.py +73 -0
- wcc_sim-0.1.0/tests/test_pipeline.py +235 -0
- wcc_sim-0.1.0/tests/test_plotting.py +182 -0
- wcc_sim-0.1.0/tests/test_psf.py +154 -0
- wcc_sim-0.1.0/tests/test_psfreport.py +462 -0
- wcc_sim-0.1.0/tests/test_render.py +129 -0
- wcc_sim-0.1.0/tests/test_scatter.py +708 -0
- wcc_sim-0.1.0/tests/test_starflux.py +340 -0
- wcc_sim-0.1.0/tests/test_wcsutil.py +41 -0
- wcc_sim-0.1.0/tests/test_wings.py +196 -0
wcc_sim-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Schmidt Observatories
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
wcc_sim-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wcc-sim
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: End-to-end photometric image simulator for the Lazuli Wide-field Context Camera (WCC)
|
|
5
|
+
Author-email: Gudmundur Stefansson <gstefansson@schmidtsciences.org>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/schmidt-observatories/wcc-sim
|
|
8
|
+
Project-URL: Documentation, https://wcc-sim.readthedocs.io/
|
|
9
|
+
Project-URL: Issues, https://github.com/schmidt-observatories/wcc-sim/issues
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: wcc_etc>=0.1.0
|
|
14
|
+
Requires-Dist: numpy>=1.24
|
|
15
|
+
Requires-Dist: scipy>=1.10
|
|
16
|
+
Requires-Dist: astropy>=5.3
|
|
17
|
+
Requires-Dist: astroquery>=0.4.7
|
|
18
|
+
Requires-Dist: synphot>=1.2
|
|
19
|
+
Requires-Dist: photutils>=2.0
|
|
20
|
+
Requires-Dist: dust_extinction>=1.2
|
|
21
|
+
Requires-Dist: matplotlib>=3.7
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest; extra == "test"
|
|
24
|
+
Requires-Dist: ruff; extra == "test"
|
|
25
|
+
Provides-Extra: docs
|
|
26
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
27
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
28
|
+
Requires-Dist: nbsphinx; extra == "docs"
|
|
29
|
+
Requires-Dist: myst-parser; extra == "docs"
|
|
30
|
+
Requires-Dist: ipykernel; extra == "docs"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
[](https://github.com/schmidt-observatories/wcc-sim/actions/workflows/tests.yml)
|
|
36
|
+
|
|
37
|
+
# wcc-sim
|
|
38
|
+
End-to-end image simulator and differential-photometry pipeline for the
|
|
39
|
+
Wide-field Context Camera (WCC) on Lazuli.
|
|
40
|
+
|
|
41
|
+
`wcc-sim` queries Gaia DR3 for a pointing, converts Gaia photometry to detector
|
|
42
|
+
count rates through the [wcc-etc](https://github.com/schmidt-observatories/wcc-etc)
|
|
43
|
+
instrument model, renders every star with the in-focus (Airy) or +1/+2-wave
|
|
44
|
+
defocus (Zemax Huygens) PSF plus its diffraction wings and the scattered-light
|
|
45
|
+
halo, adds photon/sky/dark/read noise with full-well and ADC saturation, and
|
|
46
|
+
writes a multi-extension FITS image (SCI + SATMASK + CAT + CLEAN) with a TAN
|
|
47
|
+
WCS. The sibling `wcc-phot` package extracts a relative light curve from a
|
|
48
|
+
series of such frames.
|
|
49
|
+
|
|
50
|
+
# Installation
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pip install wcc-sim
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
[`wcc_etc`](https://pypi.org/project/wcc-etc/) provides the instrument model
|
|
57
|
+
and is pulled in from PyPI as a dependency. From source:
|
|
58
|
+
```sh
|
|
59
|
+
git clone https://github.com/schmidt-observatories/wcc-sim.git
|
|
60
|
+
cd wcc-sim
|
|
61
|
+
pip install -e .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With optional extras:
|
|
65
|
+
```sh
|
|
66
|
+
pip install -e ".[test]" # pytest
|
|
67
|
+
pip install -e ".[docs]" # documentation build (Sphinx, nbsphinx, etc.)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Python 3.11 or newer is required. A default run queries the Gaia archive, so
|
|
71
|
+
it needs network access. Pass `cache_dir=` (CLI: `--cache-dir`) to keep the
|
|
72
|
+
Gaia result on disk so repeat runs work offline.
|
|
73
|
+
|
|
74
|
+
# Quick Start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from wcc_sim import simulate_field
|
|
78
|
+
|
|
79
|
+
# Simulate one 90 s frame of the field at (RA, Dec) = (150.1, 2.2) deg on the
|
|
80
|
+
# Sony IMX455 detector in the r band, +1 wave defocused, and save it as FITS.
|
|
81
|
+
field = simulate_field(150.1, 2.2, sensorfilter="zwo:r", focus=1,
|
|
82
|
+
exptime=90, seed=42, output="field_1wave.fits")
|
|
83
|
+
|
|
84
|
+
field.image_adu # the detector image (ADU)
|
|
85
|
+
field.catalog # the Gaia table with pixel positions and count rates
|
|
86
|
+
field.params # everything the run used (rates, noise, PSF, wing floor)
|
|
87
|
+
field.plot(catalog=True) # the image with the catalog circled (ax=None -> new figure)
|
|
88
|
+
field.plot_psf_profile() # Airy core, scatter halo and the rendered radial profile
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The same from the command line:
|
|
92
|
+
```sh
|
|
93
|
+
wcc-sim --ra 150.1 --dec 2.2 --sensorfilter zwo:r --focus 1 \
|
|
94
|
+
--exptime 90 --seed 42 -o field_1wave.fits
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then recover a light curve from a series of frames of the same field:
|
|
98
|
+
```python
|
|
99
|
+
from wcc_phot import run_photometry
|
|
100
|
+
|
|
101
|
+
result = run_photometry(["f0.fits", "f1.fits", "f2.fits"], target=(150.1, 2.2),
|
|
102
|
+
method="aperture", n_ref=10)
|
|
103
|
+
result.lightcurve # per frame: rel_flux_norm, errors, flags
|
|
104
|
+
result.plot() # the relative light curve; result.plot_field(frame), .report(path)
|
|
105
|
+
```
|
|
106
|
+
```sh
|
|
107
|
+
wcc-phot f*.fits --ra 150.1 --dec 2.2 --method psf -o phot.fits --report report.pdf
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`sensorfilter` is `kind:band` as in wcc-etc: `zwo:*` is the Sony IMX455
|
|
111
|
+
(9568 x 6380 px, 16.87 mas/pix) and `qcmos:*` the Hamamatsu HWK4123
|
|
112
|
+
(4096 x 2304 px, 20.64 mas/pix). `focus=0|1|2` selects in-focus, +1 wave or
|
|
113
|
+
+2 waves of defocus. `extended_sources=[SersicComponent(...)]` adds analytic
|
|
114
|
+
galaxy light and `chromatic=True` switches to spectrum-weighted PSFs (in focus
|
|
115
|
+
only).
|
|
116
|
+
|
|
117
|
+
# Tutorial
|
|
118
|
+
See the `notebooks/` directory for example tutorials (executed outputs
|
|
119
|
+
included; the Gaia queries they need are cached in `notebooks/gaia_cache/`,
|
|
120
|
+
so they run offline):
|
|
121
|
+
|
|
122
|
+
| Notebook | What it covers |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `01_getting_started.ipynb` | Simulate a full frame, overlay the Gaia catalog, compare the three PSF modes on one star, and write the FITS file. |
|
|
125
|
+
| `02_verification.ipynb` | The checks: PSF peak ordering, astrometric residuals, photometric closure on the ETC rates, noise statistics, saturation flags, and the FITS round trip. |
|
|
126
|
+
| `03_psf_focus_modes.ipynb` | In-focus Airy vs +1/+2-wave defocus PSFs and what they do to peak pixel and saturation. |
|
|
127
|
+
| `04_detectors_and_filters.ipynb` | The two detectors, the filter sets, and Gaia-to-count-rate conversion per spectral type. |
|
|
128
|
+
| `05_noise_and_saturation.ipynb` | Photon, sky, dark and read noise; full well and ADC clipping; the SATMASK extension. |
|
|
129
|
+
| `06_astrometry_and_catalogs.ipynb` | The TAN WCS and its round trip, where stars land, position angle, edges and user-supplied catalogs. |
|
|
130
|
+
| `07_cli_and_fits.ipynb` | The `wcc-sim` command line and the layout of the output FITS file. |
|
|
131
|
+
| `08_psf_wings.ipynb` | Diffraction wings beyond the PSF stamp: the wing model, seam smoothness, encircled energy and how far stars extend. |
|
|
132
|
+
| `09_wcc_phot_photometry.ipynb` | `wcc-phot`: simulate a dithered series, pick a target, aperture and PSF photometry, centroid drift, the live viewer. |
|
|
133
|
+
| `10_plotting.ipynb` | Every `.plot_*` method once: images with catalog and saturation overlays, cutouts, the PSF radial profile, stamps, encircled energy, the photometry panels. |
|
|
134
|
+
|
|
135
|
+
Ready-to-run CLI examples are in `scripts/example_*.sh`. Two end-to-end
|
|
136
|
+
validations live next to them: `scripts/transit_trappist1b.py` injects the
|
|
137
|
+
TRAPPIST-1b transit into the Gaia catalog, simulates 36 x 300 s defocused
|
|
138
|
+
frames, runs `wcc-phot` and checks the recovered depth; `scripts/cepheids_m101.py`
|
|
139
|
+
places synthetic Cepheids on the M101 disk (Sersic galaxy light plus the real
|
|
140
|
+
Gaia foreground) and recovers a period-luminosity relation.
|
|
141
|
+
|
|
142
|
+
# Notes
|
|
143
|
+
Instrument configuration (detectors, filters, throughput, PSFs, the stray-light
|
|
144
|
+
model) comes from wcc-etc and is not duplicated here. The one data product
|
|
145
|
+
wcc-sim ships is the azimuthally averaged scattered-light profile in
|
|
146
|
+
`src/wcc_sim/data/psfs/`, regenerated by `scripts/build_scatter_profile.py`.
|
|
147
|
+
|
|
148
|
+
## Model assumptions and validation status
|
|
149
|
+
|
|
150
|
+
wcc-sim takes its instrument model from wcc-etc. Some choices in that model
|
|
151
|
+
are deliberate margins or simplifications rather than best estimates, and
|
|
152
|
+
they move the numbers you get:
|
|
153
|
+
|
|
154
|
+
- **Read noise carries a x2 margin.** Both detector configurations multiply
|
|
155
|
+
the datasheet read noise by `read_noise_margin = 2` to cover unmodelled
|
|
156
|
+
noise sources (RTS, non-linearity), and every simulated frame inherits it.
|
|
157
|
+
In a 90 s `zwo:r` frame most of the per-pixel variance is read noise, so
|
|
158
|
+
faint-source depth is about 0.6 mag shallower than the datasheet value
|
|
159
|
+
would give. There is no switch for it yet (issue #23).
|
|
160
|
+
- **Unobscured pupil.** The collecting area is pi (D/2)^2 and the in-focus
|
|
161
|
+
Airy PSF has no central obscuration or spiders. The defocused PSFs are
|
|
162
|
+
Zemax Huygens images of the real system.
|
|
163
|
+
- **Constant zodiacal background** at V = 22.5 mag/arcsec^2, with no
|
|
164
|
+
dependence on ecliptic latitude or Sun angle.
|
|
165
|
+
- **Gaia G normalisation** uses a synthetic Vega-system zero point about 1%
|
|
166
|
+
below the published DR3 value, so all count rates are about 1% low.
|
|
167
|
+
- **The scattered-light halo is monochromatic.** It comes from a single
|
|
168
|
+
450 nm stray-light run and is applied with the same shape and fraction in
|
|
169
|
+
every band; surface scatter falls with wavelength, so the halo is
|
|
170
|
+
over-predicted in the red bands.
|
|
171
|
+
- **"N waves" of defocus** is a Fringe Zernike Z4 coefficient of N waves
|
|
172
|
+
(2N waves peak-to-valley), the convention of the Zemax runs.
|
|
173
|
+
|
|
174
|
+
Not modelled: cosmic rays, PRNU, non-linearity, random telegraph noise,
|
|
175
|
+
charge diffusion and intrapixel response, Earthshine, the pointing dependence
|
|
176
|
+
of the zodiacal light, and giant or reddened-star templates (every star gets
|
|
177
|
+
a dwarf template).
|
|
178
|
+
|
|
179
|
+
**Validation status.** The count-rate chain, PSF energy, astrometry and
|
|
180
|
+
noise model have been checked against first-principles calculations and
|
|
181
|
+
against the wcc-etc exposure-time calculator, including a Monte Carlo
|
|
182
|
+
cross-check of the ETC's signal and noise predictions on simulated frames.
|
|
183
|
+
Nothing has yet been compared with measured data; issue #25 holds the plan.
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
The full documentation site (installation, a user guide, rendered tutorial
|
|
188
|
+
notebooks, a CLI reference and an auto-generated API reference) is built with
|
|
189
|
+
**Sphinx** and the *Read the Docs* theme. It lives under `docs/sphinx/`:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pip install -e ".[docs]" # Sphinx + theme + nbsphinx (+ the package itself)
|
|
193
|
+
brew install pandoc # or: conda install pandoc / apt-get install pandoc
|
|
194
|
+
cd docs/sphinx
|
|
195
|
+
make html # output in _build/html/index.html
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The rendered site is published on Read the Docs at
|
|
199
|
+
<https://wcc-sim.readthedocs.io/>; `.readthedocs.yaml` configures the build.
|
|
200
|
+
|
|
201
|
+
## Tests
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
pip install -e ".[test]"
|
|
205
|
+
python -m pytest
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Acknowledgements
|
|
209
|
+
This work has made use of data from the European Space Agency (ESA) mission
|
|
210
|
+
*Gaia* (https://www.cosmos.esa.int/gaia), processed by the Gaia Data
|
|
211
|
+
Processing and Analysis Consortium (DPAC); the Gaia tables redistributed in
|
|
212
|
+
`notebooks/gaia_cache/` are subject to the
|
|
213
|
+
[Gaia data credits](https://www.cosmos.esa.int/web/gaia-users/credits).
|
|
214
|
+
Stellar spectra are the Pickles (1998, PASP 110, 863) library; the Gaia
|
|
215
|
+
passbands come from the SVO Filter Profile Service (Rodrigo & Solano 2020,
|
|
216
|
+
http://svo2.cab.inta-csic.es/theory/fps/); reddening uses the Fitzpatrick
|
|
217
|
+
(1999) curve via [dust_extinction](https://dust-extinction.readthedocs.io).
|
|
218
|
+
Simulations are built on [Astropy](https://www.astropy.org),
|
|
219
|
+
[photutils](https://photutils.readthedocs.io) and
|
|
220
|
+
[synphot](https://synphot.readthedocs.io).
|
wcc_sim-0.1.0/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://github.com/schmidt-observatories/wcc-sim/actions/workflows/tests.yml)
|
|
4
|
+
|
|
5
|
+
# wcc-sim
|
|
6
|
+
End-to-end image simulator and differential-photometry pipeline for the
|
|
7
|
+
Wide-field Context Camera (WCC) on Lazuli.
|
|
8
|
+
|
|
9
|
+
`wcc-sim` queries Gaia DR3 for a pointing, converts Gaia photometry to detector
|
|
10
|
+
count rates through the [wcc-etc](https://github.com/schmidt-observatories/wcc-etc)
|
|
11
|
+
instrument model, renders every star with the in-focus (Airy) or +1/+2-wave
|
|
12
|
+
defocus (Zemax Huygens) PSF plus its diffraction wings and the scattered-light
|
|
13
|
+
halo, adds photon/sky/dark/read noise with full-well and ADC saturation, and
|
|
14
|
+
writes a multi-extension FITS image (SCI + SATMASK + CAT + CLEAN) with a TAN
|
|
15
|
+
WCS. The sibling `wcc-phot` package extracts a relative light curve from a
|
|
16
|
+
series of such frames.
|
|
17
|
+
|
|
18
|
+
# Installation
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install wcc-sim
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
[`wcc_etc`](https://pypi.org/project/wcc-etc/) provides the instrument model
|
|
25
|
+
and is pulled in from PyPI as a dependency. From source:
|
|
26
|
+
```sh
|
|
27
|
+
git clone https://github.com/schmidt-observatories/wcc-sim.git
|
|
28
|
+
cd wcc-sim
|
|
29
|
+
pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
With optional extras:
|
|
33
|
+
```sh
|
|
34
|
+
pip install -e ".[test]" # pytest
|
|
35
|
+
pip install -e ".[docs]" # documentation build (Sphinx, nbsphinx, etc.)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Python 3.11 or newer is required. A default run queries the Gaia archive, so
|
|
39
|
+
it needs network access. Pass `cache_dir=` (CLI: `--cache-dir`) to keep the
|
|
40
|
+
Gaia result on disk so repeat runs work offline.
|
|
41
|
+
|
|
42
|
+
# Quick Start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from wcc_sim import simulate_field
|
|
46
|
+
|
|
47
|
+
# Simulate one 90 s frame of the field at (RA, Dec) = (150.1, 2.2) deg on the
|
|
48
|
+
# Sony IMX455 detector in the r band, +1 wave defocused, and save it as FITS.
|
|
49
|
+
field = simulate_field(150.1, 2.2, sensorfilter="zwo:r", focus=1,
|
|
50
|
+
exptime=90, seed=42, output="field_1wave.fits")
|
|
51
|
+
|
|
52
|
+
field.image_adu # the detector image (ADU)
|
|
53
|
+
field.catalog # the Gaia table with pixel positions and count rates
|
|
54
|
+
field.params # everything the run used (rates, noise, PSF, wing floor)
|
|
55
|
+
field.plot(catalog=True) # the image with the catalog circled (ax=None -> new figure)
|
|
56
|
+
field.plot_psf_profile() # Airy core, scatter halo and the rendered radial profile
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The same from the command line:
|
|
60
|
+
```sh
|
|
61
|
+
wcc-sim --ra 150.1 --dec 2.2 --sensorfilter zwo:r --focus 1 \
|
|
62
|
+
--exptime 90 --seed 42 -o field_1wave.fits
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then recover a light curve from a series of frames of the same field:
|
|
66
|
+
```python
|
|
67
|
+
from wcc_phot import run_photometry
|
|
68
|
+
|
|
69
|
+
result = run_photometry(["f0.fits", "f1.fits", "f2.fits"], target=(150.1, 2.2),
|
|
70
|
+
method="aperture", n_ref=10)
|
|
71
|
+
result.lightcurve # per frame: rel_flux_norm, errors, flags
|
|
72
|
+
result.plot() # the relative light curve; result.plot_field(frame), .report(path)
|
|
73
|
+
```
|
|
74
|
+
```sh
|
|
75
|
+
wcc-phot f*.fits --ra 150.1 --dec 2.2 --method psf -o phot.fits --report report.pdf
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`sensorfilter` is `kind:band` as in wcc-etc: `zwo:*` is the Sony IMX455
|
|
79
|
+
(9568 x 6380 px, 16.87 mas/pix) and `qcmos:*` the Hamamatsu HWK4123
|
|
80
|
+
(4096 x 2304 px, 20.64 mas/pix). `focus=0|1|2` selects in-focus, +1 wave or
|
|
81
|
+
+2 waves of defocus. `extended_sources=[SersicComponent(...)]` adds analytic
|
|
82
|
+
galaxy light and `chromatic=True` switches to spectrum-weighted PSFs (in focus
|
|
83
|
+
only).
|
|
84
|
+
|
|
85
|
+
# Tutorial
|
|
86
|
+
See the `notebooks/` directory for example tutorials (executed outputs
|
|
87
|
+
included; the Gaia queries they need are cached in `notebooks/gaia_cache/`,
|
|
88
|
+
so they run offline):
|
|
89
|
+
|
|
90
|
+
| Notebook | What it covers |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `01_getting_started.ipynb` | Simulate a full frame, overlay the Gaia catalog, compare the three PSF modes on one star, and write the FITS file. |
|
|
93
|
+
| `02_verification.ipynb` | The checks: PSF peak ordering, astrometric residuals, photometric closure on the ETC rates, noise statistics, saturation flags, and the FITS round trip. |
|
|
94
|
+
| `03_psf_focus_modes.ipynb` | In-focus Airy vs +1/+2-wave defocus PSFs and what they do to peak pixel and saturation. |
|
|
95
|
+
| `04_detectors_and_filters.ipynb` | The two detectors, the filter sets, and Gaia-to-count-rate conversion per spectral type. |
|
|
96
|
+
| `05_noise_and_saturation.ipynb` | Photon, sky, dark and read noise; full well and ADC clipping; the SATMASK extension. |
|
|
97
|
+
| `06_astrometry_and_catalogs.ipynb` | The TAN WCS and its round trip, where stars land, position angle, edges and user-supplied catalogs. |
|
|
98
|
+
| `07_cli_and_fits.ipynb` | The `wcc-sim` command line and the layout of the output FITS file. |
|
|
99
|
+
| `08_psf_wings.ipynb` | Diffraction wings beyond the PSF stamp: the wing model, seam smoothness, encircled energy and how far stars extend. |
|
|
100
|
+
| `09_wcc_phot_photometry.ipynb` | `wcc-phot`: simulate a dithered series, pick a target, aperture and PSF photometry, centroid drift, the live viewer. |
|
|
101
|
+
| `10_plotting.ipynb` | Every `.plot_*` method once: images with catalog and saturation overlays, cutouts, the PSF radial profile, stamps, encircled energy, the photometry panels. |
|
|
102
|
+
|
|
103
|
+
Ready-to-run CLI examples are in `scripts/example_*.sh`. Two end-to-end
|
|
104
|
+
validations live next to them: `scripts/transit_trappist1b.py` injects the
|
|
105
|
+
TRAPPIST-1b transit into the Gaia catalog, simulates 36 x 300 s defocused
|
|
106
|
+
frames, runs `wcc-phot` and checks the recovered depth; `scripts/cepheids_m101.py`
|
|
107
|
+
places synthetic Cepheids on the M101 disk (Sersic galaxy light plus the real
|
|
108
|
+
Gaia foreground) and recovers a period-luminosity relation.
|
|
109
|
+
|
|
110
|
+
# Notes
|
|
111
|
+
Instrument configuration (detectors, filters, throughput, PSFs, the stray-light
|
|
112
|
+
model) comes from wcc-etc and is not duplicated here. The one data product
|
|
113
|
+
wcc-sim ships is the azimuthally averaged scattered-light profile in
|
|
114
|
+
`src/wcc_sim/data/psfs/`, regenerated by `scripts/build_scatter_profile.py`.
|
|
115
|
+
|
|
116
|
+
## Model assumptions and validation status
|
|
117
|
+
|
|
118
|
+
wcc-sim takes its instrument model from wcc-etc. Some choices in that model
|
|
119
|
+
are deliberate margins or simplifications rather than best estimates, and
|
|
120
|
+
they move the numbers you get:
|
|
121
|
+
|
|
122
|
+
- **Read noise carries a x2 margin.** Both detector configurations multiply
|
|
123
|
+
the datasheet read noise by `read_noise_margin = 2` to cover unmodelled
|
|
124
|
+
noise sources (RTS, non-linearity), and every simulated frame inherits it.
|
|
125
|
+
In a 90 s `zwo:r` frame most of the per-pixel variance is read noise, so
|
|
126
|
+
faint-source depth is about 0.6 mag shallower than the datasheet value
|
|
127
|
+
would give. There is no switch for it yet (issue #23).
|
|
128
|
+
- **Unobscured pupil.** The collecting area is pi (D/2)^2 and the in-focus
|
|
129
|
+
Airy PSF has no central obscuration or spiders. The defocused PSFs are
|
|
130
|
+
Zemax Huygens images of the real system.
|
|
131
|
+
- **Constant zodiacal background** at V = 22.5 mag/arcsec^2, with no
|
|
132
|
+
dependence on ecliptic latitude or Sun angle.
|
|
133
|
+
- **Gaia G normalisation** uses a synthetic Vega-system zero point about 1%
|
|
134
|
+
below the published DR3 value, so all count rates are about 1% low.
|
|
135
|
+
- **The scattered-light halo is monochromatic.** It comes from a single
|
|
136
|
+
450 nm stray-light run and is applied with the same shape and fraction in
|
|
137
|
+
every band; surface scatter falls with wavelength, so the halo is
|
|
138
|
+
over-predicted in the red bands.
|
|
139
|
+
- **"N waves" of defocus** is a Fringe Zernike Z4 coefficient of N waves
|
|
140
|
+
(2N waves peak-to-valley), the convention of the Zemax runs.
|
|
141
|
+
|
|
142
|
+
Not modelled: cosmic rays, PRNU, non-linearity, random telegraph noise,
|
|
143
|
+
charge diffusion and intrapixel response, Earthshine, the pointing dependence
|
|
144
|
+
of the zodiacal light, and giant or reddened-star templates (every star gets
|
|
145
|
+
a dwarf template).
|
|
146
|
+
|
|
147
|
+
**Validation status.** The count-rate chain, PSF energy, astrometry and
|
|
148
|
+
noise model have been checked against first-principles calculations and
|
|
149
|
+
against the wcc-etc exposure-time calculator, including a Monte Carlo
|
|
150
|
+
cross-check of the ETC's signal and noise predictions on simulated frames.
|
|
151
|
+
Nothing has yet been compared with measured data; issue #25 holds the plan.
|
|
152
|
+
|
|
153
|
+
## Documentation
|
|
154
|
+
|
|
155
|
+
The full documentation site (installation, a user guide, rendered tutorial
|
|
156
|
+
notebooks, a CLI reference and an auto-generated API reference) is built with
|
|
157
|
+
**Sphinx** and the *Read the Docs* theme. It lives under `docs/sphinx/`:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pip install -e ".[docs]" # Sphinx + theme + nbsphinx (+ the package itself)
|
|
161
|
+
brew install pandoc # or: conda install pandoc / apt-get install pandoc
|
|
162
|
+
cd docs/sphinx
|
|
163
|
+
make html # output in _build/html/index.html
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The rendered site is published on Read the Docs at
|
|
167
|
+
<https://wcc-sim.readthedocs.io/>; `.readthedocs.yaml` configures the build.
|
|
168
|
+
|
|
169
|
+
## Tests
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
pip install -e ".[test]"
|
|
173
|
+
python -m pytest
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Acknowledgements
|
|
177
|
+
This work has made use of data from the European Space Agency (ESA) mission
|
|
178
|
+
*Gaia* (https://www.cosmos.esa.int/gaia), processed by the Gaia Data
|
|
179
|
+
Processing and Analysis Consortium (DPAC); the Gaia tables redistributed in
|
|
180
|
+
`notebooks/gaia_cache/` are subject to the
|
|
181
|
+
[Gaia data credits](https://www.cosmos.esa.int/web/gaia-users/credits).
|
|
182
|
+
Stellar spectra are the Pickles (1998, PASP 110, 863) library; the Gaia
|
|
183
|
+
passbands come from the SVO Filter Profile Service (Rodrigo & Solano 2020,
|
|
184
|
+
http://svo2.cab.inta-csic.es/theory/fps/); reddening uses the Fitzpatrick
|
|
185
|
+
(1999) curve via [dust_extinction](https://dust-extinction.readthedocs.io).
|
|
186
|
+
Simulations are built on [Astropy](https://www.astropy.org),
|
|
187
|
+
[photutils](https://photutils.readthedocs.io) and
|
|
188
|
+
[synphot](https://synphot.readthedocs.io).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wcc-sim"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "End-to-end photometric image simulator for the Lazuli Wide-field Context Camera (WCC)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "BSD-3-Clause"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Gudmundur Stefansson", email = "gstefansson@schmidtsciences.org" },
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.11"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"wcc_etc>=0.1.0", # instrument model; public io names since 0.1.0
|
|
18
|
+
"numpy>=1.24",
|
|
19
|
+
"scipy>=1.10",
|
|
20
|
+
"astropy>=5.3",
|
|
21
|
+
"astroquery>=0.4.7",
|
|
22
|
+
"synphot>=1.2",
|
|
23
|
+
"photutils>=2.0", # ImagePSF
|
|
24
|
+
"dust_extinction>=1.2",
|
|
25
|
+
"matplotlib>=3.7", # wcc_phot report/live view, wcc_sim.psfreport
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/schmidt-observatories/wcc-sim"
|
|
30
|
+
Documentation = "https://wcc-sim.readthedocs.io/"
|
|
31
|
+
Issues = "https://github.com/schmidt-observatories/wcc-sim/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
wcc-sim = "wcc_sim.cli:main"
|
|
35
|
+
wcc-phot = "wcc_phot.cli:main"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
test = ["pytest", "ruff"]
|
|
39
|
+
docs = [
|
|
40
|
+
"sphinx",
|
|
41
|
+
"sphinx-rtd-theme",
|
|
42
|
+
"nbsphinx",
|
|
43
|
+
"myst-parser",
|
|
44
|
+
"ipykernel",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.package-data]
|
|
51
|
+
wcc_sim = ["data/*", "data/psfs/*.ecsv"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
# `pytest -m "not slow"` skips the Monte Carlo cross-check (~30 s); nothing
|
|
56
|
+
# in the suite needs the network, `network` is reserved for tests that will.
|
|
57
|
+
markers = [
|
|
58
|
+
"slow: takes more than ~10 s (Monte Carlo cross-checks)",
|
|
59
|
+
"network: needs the Gaia archive or VizieR",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
# Pin the rule set so local runs and CI agree whatever ruff defaults do:
|
|
64
|
+
# pyflakes plus the pycodestyle error classes ruff enables by default.
|
|
65
|
+
select = ["E4", "E7", "E9", "F"]
|
wcc_sim-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Aperture photometry with CCD-equation errors from the frame header rates."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from astropy.stats import SigmaClip
|
|
5
|
+
from photutils.aperture import ApertureStats, CircularAnnulus, CircularAperture
|
|
6
|
+
|
|
7
|
+
from .flags import FLAG_EDGE, FLAG_SATURATED
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _saturated_within(satmask, x, y, radius):
|
|
11
|
+
"""True if any saturated pixel lies within `radius` px of (x, y)."""
|
|
12
|
+
ny, nx = satmask.shape
|
|
13
|
+
r = int(np.ceil(radius))
|
|
14
|
+
x0, y0 = int(round(x)), int(round(y))
|
|
15
|
+
y_lo, y_hi = max(y0 - r, 0), min(y0 + r + 1, ny)
|
|
16
|
+
x_lo, x_hi = max(x0 - r, 0), min(x0 + r + 1, nx)
|
|
17
|
+
if y_lo >= y_hi or x_lo >= x_hi:
|
|
18
|
+
return False
|
|
19
|
+
return bool(satmask[y_lo:y_hi, x_lo:x_hi].any())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def aperture_photometry_frame(frame, x, y, geom):
|
|
23
|
+
"""Background-subtracted aperture fluxes at (x, y) on the electron image.
|
|
24
|
+
|
|
25
|
+
Returns (flux_e, flux_err_e, bkg_e_pix, flags). Background is the
|
|
26
|
+
sigma-clipped annulus median; the variance is Poisson of the star plus
|
|
27
|
+
the per-pixel background variance from the header rates
|
|
28
|
+
((sky+dark)*exptime + n_reads*read_noise^2), including the
|
|
29
|
+
background-estimate term (area^2/n_annulus).
|
|
30
|
+
"""
|
|
31
|
+
x = np.atleast_1d(np.asarray(x, dtype=float))
|
|
32
|
+
y = np.atleast_1d(np.asarray(y, dtype=float))
|
|
33
|
+
positions = np.column_stack([x, y])
|
|
34
|
+
aper = CircularAperture(positions, r=geom.r_ap)
|
|
35
|
+
annulus = CircularAnnulus(positions, r_in=geom.r_in, r_out=geom.r_out)
|
|
36
|
+
|
|
37
|
+
bkg = np.asarray(
|
|
38
|
+
ApertureStats(
|
|
39
|
+
frame.image_e, annulus, sigma_clip=SigmaClip(sigma=3.0)
|
|
40
|
+
).median,
|
|
41
|
+
dtype=float,
|
|
42
|
+
)
|
|
43
|
+
raw = np.asarray(ApertureStats(frame.image_e, aper).sum, dtype=float)
|
|
44
|
+
area = aper.area
|
|
45
|
+
flux = raw - bkg * area
|
|
46
|
+
|
|
47
|
+
m = frame.meta
|
|
48
|
+
var_pix = (
|
|
49
|
+
(float(m["sky_e_s"]) + float(m["dark_e_s"])) * float(m["exptime"])
|
|
50
|
+
+ int(m["n_reads"]) * float(m["read_noise"]) ** 2
|
|
51
|
+
)
|
|
52
|
+
variance = np.clip(flux, 0, None) + area * var_pix * (
|
|
53
|
+
1.0 + area / annulus.area
|
|
54
|
+
)
|
|
55
|
+
flux_err = np.sqrt(variance)
|
|
56
|
+
|
|
57
|
+
ny, nx = frame.image_e.shape
|
|
58
|
+
flags = np.zeros(x.size, dtype=int)
|
|
59
|
+
edge = (
|
|
60
|
+
(x < geom.r_out)
|
|
61
|
+
| (x > nx - 1 - geom.r_out)
|
|
62
|
+
| (y < geom.r_out)
|
|
63
|
+
| (y > ny - 1 - geom.r_out)
|
|
64
|
+
)
|
|
65
|
+
flags[edge] |= FLAG_EDGE
|
|
66
|
+
for i in range(x.size):
|
|
67
|
+
if _saturated_within(frame.satmask, x[i], y[i], geom.r_ap):
|
|
68
|
+
flags[i] |= FLAG_SATURATED
|
|
69
|
+
return flux, flux_err, bkg, flags
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Per-frame re-centroiding of WCS-predicted star positions.
|
|
2
|
+
|
|
3
|
+
Center-of-mass (not a Gaussian fit) because the +1/+2-wave defocus PSFs are
|
|
4
|
+
centrally-depressed donuts; COM in a box sized to the PSF works for both.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from photutils.centroids import centroid_com, centroid_sources
|
|
11
|
+
|
|
12
|
+
from .flags import FLAG_CENTROID
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def centroid_stars(frame, x0, y0, box):
|
|
16
|
+
"""Refine (x0, y0) with a background-subtracted COM in a `box` px window.
|
|
17
|
+
|
|
18
|
+
Returns (x, y, flags); a failed or runaway centroid (>box px from the
|
|
19
|
+
start) falls back to the input position with FLAG_CENTROID set.
|
|
20
|
+
"""
|
|
21
|
+
x0 = np.atleast_1d(np.asarray(x0, dtype=float))
|
|
22
|
+
y0 = np.atleast_1d(np.asarray(y0, dtype=float))
|
|
23
|
+
data = frame.image_e - np.median(frame.image_e)
|
|
24
|
+
ny, nx = data.shape
|
|
25
|
+
x_start = np.clip(x0, 0, nx - 1)
|
|
26
|
+
y_start = np.clip(y0, 0, ny - 1)
|
|
27
|
+
with warnings.catch_warnings():
|
|
28
|
+
warnings.simplefilter("ignore") # partial-overlap warnings at edges
|
|
29
|
+
x, y = centroid_sources(
|
|
30
|
+
data, x_start, y_start, box_size=box, centroid_func=centroid_com
|
|
31
|
+
)
|
|
32
|
+
flags = np.zeros(x0.size, dtype=int)
|
|
33
|
+
bad = (
|
|
34
|
+
~np.isfinite(x)
|
|
35
|
+
| ~np.isfinite(y)
|
|
36
|
+
| (np.hypot(x - x_start, y - y_start) > box)
|
|
37
|
+
)
|
|
38
|
+
flags[bad] |= FLAG_CENTROID
|
|
39
|
+
x[bad] = x0[bad]
|
|
40
|
+
y[bad] = y0[bad]
|
|
41
|
+
return x, y, flags
|