asteroidpipeline 0.2.1__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.
- asteroidpipeline-0.2.1/.gitignore +49 -0
- asteroidpipeline-0.2.1/LICENSE +21 -0
- asteroidpipeline-0.2.1/PKG-INFO +95 -0
- asteroidpipeline-0.2.1/Project.toml +53 -0
- asteroidpipeline-0.2.1/README.md +71 -0
- asteroidpipeline-0.2.1/pyproject.toml +62 -0
- asteroidpipeline-0.2.1/src/AsteroidPipeline.jl +62 -0
- asteroidpipeline-0.2.1/src/astrometric_refinement.jl +230 -0
- asteroidpipeline-0.2.1/src/astrometry.jl +162 -0
- asteroidpipeline-0.2.1/src/calibrated_photometry.jl +135 -0
- asteroidpipeline-0.2.1/src/campaign.jl +702 -0
- asteroidpipeline-0.2.1/src/crossmatch.jl +434 -0
- asteroidpipeline-0.2.1/src/detection.jl +208 -0
- asteroidpipeline-0.2.1/src/digest2.jl +119 -0
- asteroidpipeline-0.2.1/src/injection.jl +181 -0
- asteroidpipeline-0.2.1/src/known_nearby.jl +156 -0
- asteroidpipeline-0.2.1/src/linking.jl +241 -0
- asteroidpipeline-0.2.1/src/linking_multinight.jl +109 -0
- asteroidpipeline-0.2.1/src/mpc_export.jl +276 -0
- asteroidpipeline-0.2.1/src/mpcorb.jl +175 -0
- asteroidpipeline-0.2.1/src/neocp.jl +149 -0
- asteroidpipeline-0.2.1/src/observatories.jl +166 -0
- asteroidpipeline-0.2.1/src/pipeline.jl +626 -0
- asteroidpipeline-0.2.1/src/platesolve.jl +144 -0
- asteroidpipeline-0.2.1/src/psf.jl +202 -0
- asteroidpipeline-0.2.1/src/reference.jl +174 -0
- asteroidpipeline-0.2.1/src/rotation.jl +89 -0
- asteroidpipeline-0.2.1/src/stack_difference.jl +316 -0
- asteroidpipeline-0.2.1/src/variables.jl +298 -0
- asteroidpipeline-0.2.1/src/zogy.jl +205 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/__init__.py +3 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/__main__.py +3 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/app.py +89 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/backend.py +126 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/clipboard.py +27 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/config.py +58 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/designations.py +75 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/engine/__init__.py +129 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/engine/server.jl +291 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/imaging.py +181 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/labels.py +122 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/profiles.py +99 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/__init__.py +0 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/analysis.py +138 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/check.py +134 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/help.py +28 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/home.py +164 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/open.py +142 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/profile.py +99 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/reason.py +40 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/report.py +118 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/results.py +411 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/settings.py +130 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/screens/summary.py +59 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/sets.py +128 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/styles.tcss +299 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/texts.py +119 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/theme.py +22 -0
- asteroidpipeline-0.2.1/ui/asteroid_ui/widgets.py +77 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Manifest.toml
|
|
2
|
+
/data/
|
|
3
|
+
*.fits
|
|
4
|
+
*.jl.cov
|
|
5
|
+
*.jl.*.cov
|
|
6
|
+
*.jl.mem
|
|
7
|
+
.vscode/
|
|
8
|
+
result
|
|
9
|
+
result-*
|
|
10
|
+
.direnv/
|
|
11
|
+
|
|
12
|
+
# docs/ — build output and DocumenterVitepress's own Node/Vitepress
|
|
13
|
+
# tooling. docs/src/*.md are real, permanent, committed pages, not
|
|
14
|
+
# generated — deliberately not listed here.
|
|
15
|
+
docs/build/
|
|
16
|
+
docs/node_modules/
|
|
17
|
+
docs/package-lock.json
|
|
18
|
+
docs/.vitepress/cache/
|
|
19
|
+
docs/.vitepress/dist/
|
|
20
|
+
docs/src/.vitepress/cache/
|
|
21
|
+
docs/src/.vitepress/dist/
|
|
22
|
+
|
|
23
|
+
# docs/src/assets/ — generated by docs/make_figures.jl on every build
|
|
24
|
+
# (local and CI), so these would just be stale duplicates if committed;
|
|
25
|
+
# not a blanket ignore of the whole assets/ directory in case a real,
|
|
26
|
+
# hand-authored logo.png/favicon.ico is ever added there.
|
|
27
|
+
docs/src/assets/systematic-error-floor-sweep.png
|
|
28
|
+
docs/src/assets/iasc-match-radius-retuning.png
|
|
29
|
+
docs/src/assets/pipeline-overview.png
|
|
30
|
+
docs/src/assets/completeness.png
|
|
31
|
+
docs/src/assets/set-quality.png
|
|
32
|
+
docs/src/assets/background-excess.png
|
|
33
|
+
docs/src/assets/astrometry-vs-astrometrica.png
|
|
34
|
+
docs/src/assets/tracklets-to-vet.png
|
|
35
|
+
docs/src/assets/nearby-geometry.png
|
|
36
|
+
|
|
37
|
+
# Local-only notes about the GitHub mirror's own CI/Pages plumbing — not
|
|
38
|
+
# part of the project (the wiki documents the pipeline, not its own
|
|
39
|
+
# deploy infrastructure).
|
|
40
|
+
PAGES_NOTES.md
|
|
41
|
+
|
|
42
|
+
# Real API keys for local test runs (e.g. ASTROMETRY_API_KEY) — see
|
|
43
|
+
# .env.example for the expected format.
|
|
44
|
+
.env
|
|
45
|
+
|
|
46
|
+
# Python packaging of the terminal app (pyproject.toml)
|
|
47
|
+
dist/
|
|
48
|
+
__pycache__/
|
|
49
|
+
*.egg-info/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alejandro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: asteroidpipeline
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Find moving objects in astronomical image sets, check them, and write MPC reports — a terminal app on AsteroidPipeline.jl
|
|
5
|
+
Project-URL: Documentation, https://richard7987.github.io/AsteroidPipeline.jl/stable/
|
|
6
|
+
Project-URL: Source, https://github.com/Richard7987/AsteroidPipeline.jl
|
|
7
|
+
Author: Alejandro
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: FITS,MPC,asteroids,astrometry,astronomy,minor planets,terminal
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: juliapkg>=0.1.13
|
|
19
|
+
Requires-Dist: pillow>=10.1
|
|
20
|
+
Requires-Dist: pyfiglet>=1.0
|
|
21
|
+
Requires-Dist: textual-image>=0.13
|
|
22
|
+
Requires-Dist: textual>=8.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# AsteroidPipeline.jl
|
|
26
|
+
|
|
27
|
+
[](https://github.com/Richard7987/AsteroidPipeline.jl/actions/workflows/CI.yml)
|
|
28
|
+
[](https://richard7987.github.io/AsteroidPipeline.jl/stable/)
|
|
29
|
+
[](https://richard7987.github.io/AsteroidPipeline.jl/dev/)
|
|
30
|
+
|
|
31
|
+
Find asteroids in astronomical images. Give it a few exposures of one field
|
|
32
|
+
taken minutes apart — a `.zip`, a folder or FITS files, from any telescope —
|
|
33
|
+
and it calibrates them against Gaia, finds what moves, tells known asteroids
|
|
34
|
+
from new candidates, and writes the report for the Minor Planet Center.
|
|
35
|
+
|
|
36
|
+
It comes two ways:
|
|
37
|
+
|
|
38
|
+
- **`asteroidpipeline`**, a terminal app: open an image set, watch the images
|
|
39
|
+
animate with every object marked, confirm or reject each candidate, copy
|
|
40
|
+
the report. Every decision is saved, with the candidate's pixels, as
|
|
41
|
+
training data for a future classifier.
|
|
42
|
+
- **AsteroidPipeline.jl**, the Julia library that does all the analysis —
|
|
43
|
+
for scripts, batches and anything the app doesn't do.
|
|
44
|
+
|
|
45
|
+
**Documentation: <https://richard7987.github.io/AsteroidPipeline.jl/stable/>** (for the latest release; [dev](https://richard7987.github.io/AsteroidPipeline.jl/dev/) for `main`)
|
|
46
|
+
|
|
47
|
+
## Install the app
|
|
48
|
+
|
|
49
|
+
On any Linux (or macOS) with Python 3.11 or newer:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
curl -fsSL https://raw.githubusercontent.com/Richard7987/AsteroidPipeline.jl/main/install.sh | sh
|
|
53
|
+
asteroidpipeline
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
or with [pipx](https://pipx.pypa.io/) (or pip), from PyPI:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pipx install asteroidpipeline
|
|
60
|
+
asteroidpipeline --setup # optional: install the analysis engine now
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The first run installs the analysis engine: Julia (downloaded if you don't
|
|
64
|
+
have it) and its packages, several minutes once. Images are shown at full
|
|
65
|
+
resolution in terminals with a graphics protocol (Kitty, WezTerm, Ghostty,
|
|
66
|
+
foot…), as coloured blocks elsewhere. With Nix: `nix run github:Richard7987/AsteroidPipeline.jl`.
|
|
67
|
+
|
|
68
|
+
## Use the library
|
|
69
|
+
|
|
70
|
+
```julia
|
|
71
|
+
using Pkg; Pkg.add(url="https://github.com/Richard7987/AsteroidPipeline.jl")
|
|
72
|
+
|
|
73
|
+
using AsteroidPipeline
|
|
74
|
+
result = analyze_set("images.zip"; observatory="F51", workdir="work")
|
|
75
|
+
print(result.report)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Many sets at once: `julia --project=. examples/analyze_sets.jl <folder> <observatory code>`.
|
|
79
|
+
Start with [Getting started](https://richard7987.github.io/AsteroidPipeline.jl/dev/getting-started).
|
|
80
|
+
|
|
81
|
+
## Repository
|
|
82
|
+
|
|
83
|
+
| Path | What |
|
|
84
|
+
|:--|:--|
|
|
85
|
+
| `src/` | the Julia library |
|
|
86
|
+
| `test/` | its tests (`julia --project=. -e 'using Pkg; Pkg.test()'`) |
|
|
87
|
+
| `docs/` | the documentation site (`julia --project=docs docs/make.jl`) |
|
|
88
|
+
| `examples/` | scripts for real data, batches and completeness tests |
|
|
89
|
+
| `ui/` | the terminal app (Python, [Textual](https://textual.textualize.io/)); see [ui/README.md](ui/README.md) |
|
|
90
|
+
| `pyproject.toml`, `install.sh` | packaging of the app, with the library inside |
|
|
91
|
+
| `flake.nix` | Nix: `nix develop` for a development shell, `nix build` for the app |
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name = "AsteroidPipeline"
|
|
2
|
+
uuid = "1001636e-a0d1-496e-b23d-cef10bc3cdca"
|
|
3
|
+
version = "0.2.1"
|
|
4
|
+
authors = ["Alejandro <ale_bnes@nezzontli.xyz>"]
|
|
5
|
+
|
|
6
|
+
[deps]
|
|
7
|
+
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
|
|
8
|
+
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
|
|
9
|
+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
|
10
|
+
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
|
11
|
+
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
|
|
12
|
+
FITSIO = "525bcba6-941b-5504-bd06-fd0dc1a4d2eb"
|
|
13
|
+
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
|
14
|
+
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
|
|
15
|
+
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
|
16
|
+
LombScargle = "fc60dff9-86e7-5f2f-a8a0-edeadbb75bd9"
|
|
17
|
+
LsqFit = "2fda8390-95c7-5789-9bda-21331edee243"
|
|
18
|
+
Photometry = "af68cb61-81ac-52ed-8703-edc140936be4"
|
|
19
|
+
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
|
20
|
+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
|
21
|
+
Reproject = "d1dcc2e6-806e-11e9-2897-3f99785db2ae"
|
|
22
|
+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
|
23
|
+
TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"
|
|
24
|
+
WCS = "15f3aee2-9e10-537f-b834-a6fb8bdb944d"
|
|
25
|
+
p7zip_jll = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
|
|
26
|
+
|
|
27
|
+
[compat]
|
|
28
|
+
CSV = "0.10.16"
|
|
29
|
+
CodecZlib = "0.7.8"
|
|
30
|
+
Dates = "1.11.0"
|
|
31
|
+
Distributed = "1.11.0"
|
|
32
|
+
FFTW = "1.10.0"
|
|
33
|
+
FITSIO = "0.17.5"
|
|
34
|
+
HTTP = "2.6.4"
|
|
35
|
+
Interpolations = "0.16.3"
|
|
36
|
+
JSON = "1.7.1"
|
|
37
|
+
LombScargle = "1.0.3"
|
|
38
|
+
LsqFit = "0.16.1"
|
|
39
|
+
Photometry = "0.9.8"
|
|
40
|
+
Printf = "1.11.0"
|
|
41
|
+
Random = "1.11.0"
|
|
42
|
+
Reproject = "0.3.3"
|
|
43
|
+
Statistics = "1.11.1"
|
|
44
|
+
TypedTables = "1.4.6"
|
|
45
|
+
WCS = "0.6.3"
|
|
46
|
+
julia = "1.11"
|
|
47
|
+
p7zip_jll = "17.7.0"
|
|
48
|
+
|
|
49
|
+
[extras]
|
|
50
|
+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
|
51
|
+
|
|
52
|
+
[targets]
|
|
53
|
+
test = ["Test"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# AsteroidPipeline.jl
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Richard7987/AsteroidPipeline.jl/actions/workflows/CI.yml)
|
|
4
|
+
[](https://richard7987.github.io/AsteroidPipeline.jl/stable/)
|
|
5
|
+
[](https://richard7987.github.io/AsteroidPipeline.jl/dev/)
|
|
6
|
+
|
|
7
|
+
Find asteroids in astronomical images. Give it a few exposures of one field
|
|
8
|
+
taken minutes apart — a `.zip`, a folder or FITS files, from any telescope —
|
|
9
|
+
and it calibrates them against Gaia, finds what moves, tells known asteroids
|
|
10
|
+
from new candidates, and writes the report for the Minor Planet Center.
|
|
11
|
+
|
|
12
|
+
It comes two ways:
|
|
13
|
+
|
|
14
|
+
- **`asteroidpipeline`**, a terminal app: open an image set, watch the images
|
|
15
|
+
animate with every object marked, confirm or reject each candidate, copy
|
|
16
|
+
the report. Every decision is saved, with the candidate's pixels, as
|
|
17
|
+
training data for a future classifier.
|
|
18
|
+
- **AsteroidPipeline.jl**, the Julia library that does all the analysis —
|
|
19
|
+
for scripts, batches and anything the app doesn't do.
|
|
20
|
+
|
|
21
|
+
**Documentation: <https://richard7987.github.io/AsteroidPipeline.jl/stable/>** (for the latest release; [dev](https://richard7987.github.io/AsteroidPipeline.jl/dev/) for `main`)
|
|
22
|
+
|
|
23
|
+
## Install the app
|
|
24
|
+
|
|
25
|
+
On any Linux (or macOS) with Python 3.11 or newer:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
curl -fsSL https://raw.githubusercontent.com/Richard7987/AsteroidPipeline.jl/main/install.sh | sh
|
|
29
|
+
asteroidpipeline
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
or with [pipx](https://pipx.pypa.io/) (or pip), from PyPI:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
pipx install asteroidpipeline
|
|
36
|
+
asteroidpipeline --setup # optional: install the analysis engine now
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The first run installs the analysis engine: Julia (downloaded if you don't
|
|
40
|
+
have it) and its packages, several minutes once. Images are shown at full
|
|
41
|
+
resolution in terminals with a graphics protocol (Kitty, WezTerm, Ghostty,
|
|
42
|
+
foot…), as coloured blocks elsewhere. With Nix: `nix run github:Richard7987/AsteroidPipeline.jl`.
|
|
43
|
+
|
|
44
|
+
## Use the library
|
|
45
|
+
|
|
46
|
+
```julia
|
|
47
|
+
using Pkg; Pkg.add(url="https://github.com/Richard7987/AsteroidPipeline.jl")
|
|
48
|
+
|
|
49
|
+
using AsteroidPipeline
|
|
50
|
+
result = analyze_set("images.zip"; observatory="F51", workdir="work")
|
|
51
|
+
print(result.report)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Many sets at once: `julia --project=. examples/analyze_sets.jl <folder> <observatory code>`.
|
|
55
|
+
Start with [Getting started](https://richard7987.github.io/AsteroidPipeline.jl/dev/getting-started).
|
|
56
|
+
|
|
57
|
+
## Repository
|
|
58
|
+
|
|
59
|
+
| Path | What |
|
|
60
|
+
|:--|:--|
|
|
61
|
+
| `src/` | the Julia library |
|
|
62
|
+
| `test/` | its tests (`julia --project=. -e 'using Pkg; Pkg.test()'`) |
|
|
63
|
+
| `docs/` | the documentation site (`julia --project=docs docs/make.jl`) |
|
|
64
|
+
| `examples/` | scripts for real data, batches and completeness tests |
|
|
65
|
+
| `ui/` | the terminal app (Python, [Textual](https://textual.textualize.io/)); see [ui/README.md](ui/README.md) |
|
|
66
|
+
| `pyproject.toml`, `install.sh` | packaging of the app, with the library inside |
|
|
67
|
+
| `flake.nix` | Nix: `nix develop` for a development shell, `nix build` for the app |
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# The terminal application: one installable program with the Julia library
|
|
2
|
+
# inside it (the wheel bundles src/ and Project.toml as
|
|
3
|
+
# asteroid_ui/julia/AsteroidPipeline). The interface's own code is ui/; the
|
|
4
|
+
# analysis is all AsteroidPipeline.jl, set up on first run (see
|
|
5
|
+
# ui/asteroid_ui/engine/__init__.py).
|
|
6
|
+
#
|
|
7
|
+
# pip install asteroidpipeline (or: pipx install asteroidpipeline)
|
|
8
|
+
# asteroidpipeline
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["hatchling"]
|
|
12
|
+
build-backend = "hatchling.build"
|
|
13
|
+
|
|
14
|
+
[project]
|
|
15
|
+
name = "asteroidpipeline"
|
|
16
|
+
dynamic = ["version"]
|
|
17
|
+
description = "Find moving objects in astronomical image sets, check them, and write MPC reports — a terminal app on AsteroidPipeline.jl"
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
license = "MIT"
|
|
20
|
+
license-files = ["LICENSE"]
|
|
21
|
+
requires-python = ">=3.11"
|
|
22
|
+
authors = [{ name = "Alejandro" }]
|
|
23
|
+
keywords = ["astronomy", "asteroids", "minor planets", "MPC", "FITS", "astrometry", "terminal"]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Environment :: Console",
|
|
26
|
+
"Intended Audience :: Science/Research",
|
|
27
|
+
"Operating System :: POSIX :: Linux",
|
|
28
|
+
"Operating System :: MacOS",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Astronomy",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"textual>=8.0",
|
|
34
|
+
"textual-image>=0.13",
|
|
35
|
+
"pillow>=10.1",
|
|
36
|
+
"pyfiglet>=1.0",
|
|
37
|
+
# Downloads Julia only when none is installed; see the engine module.
|
|
38
|
+
"juliapkg>=0.1.13",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Documentation = "https://richard7987.github.io/AsteroidPipeline.jl/stable/"
|
|
43
|
+
Source = "https://github.com/Richard7987/AsteroidPipeline.jl"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
asteroidpipeline = "asteroid_ui.app:main"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.version]
|
|
49
|
+
path = "ui/asteroid_ui/__init__.py"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["ui/asteroid_ui"]
|
|
53
|
+
exclude = ["**/__pycache__"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
56
|
+
"src" = "asteroid_ui/julia/AsteroidPipeline/src"
|
|
57
|
+
"Project.toml" = "asteroid_ui/julia/AsteroidPipeline/Project.toml"
|
|
58
|
+
"LICENSE" = "asteroid_ui/julia/AsteroidPipeline/LICENSE"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.sdist]
|
|
61
|
+
include = ["/ui/asteroid_ui", "/src", "/Project.toml", "/LICENSE", "/README.md", "/pyproject.toml"]
|
|
62
|
+
exclude = ["**/__pycache__"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module AsteroidPipeline
|
|
2
|
+
|
|
3
|
+
using FITSIO
|
|
4
|
+
using Photometry
|
|
5
|
+
using LombScargle
|
|
6
|
+
using HTTP
|
|
7
|
+
using CSV
|
|
8
|
+
using JSON
|
|
9
|
+
using TypedTables: Table
|
|
10
|
+
using WCS
|
|
11
|
+
using Printf
|
|
12
|
+
using Reproject
|
|
13
|
+
using Statistics
|
|
14
|
+
using Interpolations
|
|
15
|
+
using FFTW
|
|
16
|
+
using LsqFit
|
|
17
|
+
using Distributed
|
|
18
|
+
using Dates: DateTime, datetime2julian
|
|
19
|
+
import Dates
|
|
20
|
+
using CodecZlib: GzipDecompressorStream
|
|
21
|
+
import p7zip_jll
|
|
22
|
+
using Random: AbstractRNG, default_rng
|
|
23
|
+
|
|
24
|
+
include("detection.jl")
|
|
25
|
+
include("linking.jl")
|
|
26
|
+
include("linking_multinight.jl")
|
|
27
|
+
include("variables.jl")
|
|
28
|
+
include("astrometry.jl")
|
|
29
|
+
include("platesolve.jl")
|
|
30
|
+
include("crossmatch.jl")
|
|
31
|
+
include("astrometric_refinement.jl")
|
|
32
|
+
include("calibrated_photometry.jl")
|
|
33
|
+
include("mpcorb.jl")
|
|
34
|
+
include("campaign.jl")
|
|
35
|
+
include("observatories.jl")
|
|
36
|
+
include("known_nearby.jl")
|
|
37
|
+
include("neocp.jl")
|
|
38
|
+
include("injection.jl")
|
|
39
|
+
include("reference.jl")
|
|
40
|
+
include("psf.jl")
|
|
41
|
+
include("zogy.jl")
|
|
42
|
+
include("stack_difference.jl")
|
|
43
|
+
include("pipeline.jl")
|
|
44
|
+
include("rotation.jl")
|
|
45
|
+
include("mpc_export.jl")
|
|
46
|
+
include("digest2.jl")
|
|
47
|
+
|
|
48
|
+
export detect_sources, fill_value_mask, link_candidates, load_wcs, pix_to_sky,
|
|
49
|
+
astrometric_calibrate, crossmatch_catalog, SkyBoTServiceError, run_pipeline,
|
|
50
|
+
build_reference, load_frame, estimate_psf, fit_moffat_psf, zogy_subtract, light_curve,
|
|
51
|
+
recover_rotation_period, plate_solve, search_field, find_variable_sources,
|
|
52
|
+
variability_chi2, photometric_scale, ades_psv, mpc80_report, julian_date_to_iso8601,
|
|
53
|
+
digest2_score, link_across_nights, gaia_reference_stars, refine_wcs, stack_difference,
|
|
54
|
+
aperture_flux, photometric_zeropoint, candidate_magnitudes, frame_epoch,
|
|
55
|
+
inject_movers, injection_recovery, read_mpcorb, set_quality,
|
|
56
|
+
extract_set, clean_blank_pixels, download_mpcorb, default_mpcorb_path, astrometrica_settings,
|
|
57
|
+
mpc_report_header, blank_mpc_report, mpc_report, pack_designation, check_mpc_report,
|
|
58
|
+
known_objects_in_field, frame_footprint, analyze_set, pipeline_config, IASC_PS1_CONFIG,
|
|
59
|
+
read_obscodes, download_obscodes, frame_site, observatory_candidates, tracklet_motion,
|
|
60
|
+
nearby_known_objects, neocp_objects, neocp_check
|
|
61
|
+
|
|
62
|
+
end # module AsteroidPipeline
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gaia_reference_stars(ra, dec, radius_deg; epoch_jd::Real, mag_range=(12.0, 21.0)) -> Table
|
|
3
|
+
|
|
4
|
+
Gaia DR3 stars within `radius_deg` of `(ra, dec)` (degrees), with G
|
|
5
|
+
magnitude inside `mag_range` — a live network request, to the ESA Gaia
|
|
6
|
+
archive's TAP service (`gaiadr3.gaia_source`), falling back to VizieR's
|
|
7
|
+
copy (`I/355/gaiadr3`) if the archive fails with a network error, a
|
|
8
|
+
timeout or a 5xx status. Two services because one did go down mid-run:
|
|
9
|
+
VizieR's TAP answered 503 to every request for a sustained stretch while
|
|
10
|
+
this was being validated, which would otherwise have stopped every
|
|
11
|
+
`refine_astrometry` run cold.
|
|
12
|
+
|
|
13
|
+
Positions are propagated by proper motion from Gaia DR3's own epoch
|
|
14
|
+
(J2016.0) to `epoch_jd` (a Julian Date, normally the frame's own); stars
|
|
15
|
+
without a proper motion keep their catalogued position. A decade of proper motion is easily a
|
|
16
|
+
significant fraction of an arcsecond for nearby stars, which is the
|
|
17
|
+
whole error budget [`refine_wcs`](@ref) is trying to beat.
|
|
18
|
+
|
|
19
|
+
Returns a table with columns `ra`, `dec` (degrees, at `epoch_jd`) and
|
|
20
|
+
`gmag`, ready to pass to [`refine_wcs`](@ref).
|
|
21
|
+
"""
|
|
22
|
+
function gaia_reference_stars(ra::Real, dec::Real, radius_deg::Real;
|
|
23
|
+
epoch_jd::Real, mag_range=(12.0, 21.0))
|
|
24
|
+
0 < radius_deg <= 5 || throw(ArgumentError("radius_deg must be in (0, 5]"))
|
|
25
|
+
lo, hi = mag_range
|
|
26
|
+
# Both queries alias their columns to the same names, so one parser
|
|
27
|
+
# serves either service.
|
|
28
|
+
cone(racol, deccol) = "CONTAINS(POINT('ICRS', $racol, $deccol), " *
|
|
29
|
+
"CIRCLE('ICRS', $ra, $dec, $radius_deg)) = 1"
|
|
30
|
+
esa = "SELECT ra AS ra_deg, dec AS dec_deg, pmra AS pm_ra, pmdec AS pm_dec, " *
|
|
31
|
+
"phot_g_mean_mag AS g FROM gaiadr3.gaia_source " *
|
|
32
|
+
"WHERE $(cone("ra", "dec")) AND phot_g_mean_mag BETWEEN $lo AND $hi"
|
|
33
|
+
vizier = "SELECT RA_ICRS AS ra_deg, DE_ICRS AS dec_deg, pmRA AS pm_ra, pmDE AS pm_dec, " *
|
|
34
|
+
"Gmag AS g FROM \"I/355/gaiadr3\" " *
|
|
35
|
+
"WHERE $(cone("RA_ICRS", "DE_ICRS")) AND Gmag BETWEEN $lo AND $hi"
|
|
36
|
+
rows = try
|
|
37
|
+
_tap_query(_GAIA_ARCHIVE_TAP_URL, esa)
|
|
38
|
+
catch e
|
|
39
|
+
_is_service_failure(e) || rethrow()
|
|
40
|
+
@warn "Gaia archive unavailable; falling back to VizieR" exception=e
|
|
41
|
+
_tap_query(_VIZIER_TAP_URL, vizier)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
years = (epoch_jd - _GAIA_DR3_EPOCH_JD) / 365.25
|
|
45
|
+
out_ra = Float64[]; out_dec = Float64[]; gmag = Float64[]
|
|
46
|
+
for r in rows
|
|
47
|
+
(ismissing(r.ra_deg) || ismissing(r.dec_deg)) && continue
|
|
48
|
+
pmra = ismissing(r.pm_ra) ? 0.0 : Float64(r.pm_ra) # mas/yr, already * cos(dec)
|
|
49
|
+
pmde = ismissing(r.pm_dec) ? 0.0 : Float64(r.pm_dec)
|
|
50
|
+
d = Float64(r.dec_deg) + pmde * years / 3.6e6
|
|
51
|
+
push!(out_ra, Float64(r.ra_deg) + pmra * years / 3.6e6 / cosd(d))
|
|
52
|
+
push!(out_dec, d)
|
|
53
|
+
push!(gmag, ismissing(r.g) ? NaN : Float64(r.g))
|
|
54
|
+
end
|
|
55
|
+
return Table(ra=out_ra, dec=out_dec, gmag=gmag)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
const _GAIA_DR3_EPOCH_JD = 2457389.0 # J2016.0
|
|
59
|
+
const _GAIA_ARCHIVE_TAP_URL = "https://gea.esac.esa.int/tap-server/tap/sync"
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
refine_wcs(image, wcs, stars; threshold=10.0, mask=nothing, max_offset=60.0,
|
|
63
|
+
match_radius=2.0, min_matches=6, clip_sigma=3.0)
|
|
64
|
+
-> (wcs, refined, n_matches, rms_arcsec, offset_px, n_detections)
|
|
65
|
+
|
|
66
|
+
Re-derive a frame's astrometric solution from reference stars actually
|
|
67
|
+
detected in it, rather than trusting the header's — the same job
|
|
68
|
+
Astrometrica's "Data Reduction" does before any measurement is reported.
|
|
69
|
+
|
|
70
|
+
`image` is in the `(y, x)` order [`detect_sources`](@ref) takes; `wcs`
|
|
71
|
+
is the header's approximate solution; `stars` is a table of reference
|
|
72
|
+
stars with `ra`, `dec` columns (degrees, at the frame's epoch), e.g. from
|
|
73
|
+
[`gaia_reference_stars`](@ref). Stars are detected at `threshold` sigma
|
|
74
|
+
(`mask` is forwarded to `detect_sources`), and every catalog star is
|
|
75
|
+
projected through `wcs`. The header's error is found first as one
|
|
76
|
+
global pixel offset — the peak of a vote over every detection/star pair
|
|
77
|
+
within `max_offset` pixels — then each star is matched to the nearest
|
|
78
|
+
detection within `match_radius` pixels of its offset-corrected position,
|
|
79
|
+
and a linear tangent-plane (TAN) solution — 6 parameters: shift, scale,
|
|
80
|
+
rotation, shear — is fitted by least squares to those matches, rejecting
|
|
81
|
+
matches beyond `clip_sigma` times the residual RMS and refitting until
|
|
82
|
+
none are rejected.
|
|
83
|
+
|
|
84
|
+
Why this is needed rather than optional for real IASC/Pan-STARRS1 data:
|
|
85
|
+
on a real 2025 practice set (field `XY54_p10`), matched against 38 Gaia
|
|
86
|
+
stars, the header WCS was off by a near-constant ~26.6 px (6.8") — far
|
|
87
|
+
beyond the <1" an MPC report needs, though PS1's own `CERROR` keyword
|
|
88
|
+
claims 0.06". Refit this way, the residual RMS fell to ~0.17", and an
|
|
89
|
+
unknown object's positions agreed with Astrometrica's own
|
|
90
|
+
Gaia-calibrated measurement of it. The header's `PCA*` polynomial
|
|
91
|
+
distortion terms were checked as the explanation first and ruled out:
|
|
92
|
+
evaluated at the chip's corners they move a position by <0.5 px.
|
|
93
|
+
|
|
94
|
+
Returns a named tuple: the refined `wcs` (a plain TAN `WCSTransform`,
|
|
95
|
+
tangent point at `wcs`'s own `crval`), `refined` (`false` if fewer than
|
|
96
|
+
`min_matches` stars matched — `wcs` is then the input, unchanged — so a
|
|
97
|
+
caller can warn instead of failing outright), `n_matches`, `rms_arcsec`
|
|
98
|
+
(the fit's residual RMS; `NaN` if not refined), and `offset_px` (the
|
|
99
|
+
global `(dx, dy)` offset found, detected minus predicted), and `n_detections`
|
|
100
|
+
(sources detected at `threshold` — compared with the catalog's star count it
|
|
101
|
+
flags frames full of detector defects; see [`run_pipeline`](@ref)).
|
|
102
|
+
"""
|
|
103
|
+
function refine_wcs(image::AbstractMatrix{<:Real}, wcs::WCSTransform, stars;
|
|
104
|
+
threshold::Real=10.0, mask::Union{Nothing,AbstractMatrix{Bool}}=nothing,
|
|
105
|
+
max_offset::Real=60.0, match_radius::Real=2.0,
|
|
106
|
+
min_matches::Integer=6, clip_sigma::Real=3.0)
|
|
107
|
+
unrefined(n, offset) = (wcs=wcs, refined=false, n_matches=n, rms_arcsec=NaN, offset_px=offset,
|
|
108
|
+
n_detections=length(detections))
|
|
109
|
+
|
|
110
|
+
detections = detect_sources(image; threshold, mask)
|
|
111
|
+
det_xy = [(d.xcen, d.ycen) for d in detections]
|
|
112
|
+
n1, n2 = size(image)
|
|
113
|
+
|
|
114
|
+
predicted = Tuple{Float64,Float64,Float64,Float64}[] # (x, y, ra, dec)
|
|
115
|
+
for s in stars
|
|
116
|
+
x, y = world_to_pix(wcs, Float64[s.ra, s.dec])
|
|
117
|
+
(-max_offset < x < n2 + max_offset && -max_offset < y < n1 + max_offset) || continue
|
|
118
|
+
push!(predicted, (x, y, Float64(s.ra), Float64(s.dec)))
|
|
119
|
+
end
|
|
120
|
+
(isempty(det_xy) || isempty(predicted)) && return unrefined(0, (NaN, NaN))
|
|
121
|
+
|
|
122
|
+
offset = _vote_offset(det_xy, predicted, max_offset)
|
|
123
|
+
matches = _match_stars(det_xy, predicted, offset, match_radius)
|
|
124
|
+
length(matches) < min_matches && return unrefined(length(matches), offset)
|
|
125
|
+
|
|
126
|
+
crval = (wcs.crval[1], wcs.crval[2])
|
|
127
|
+
fit = _fit_tan(matches, crval, clip_sigma)
|
|
128
|
+
fit.n < min_matches && return unrefined(fit.n, offset)
|
|
129
|
+
return (wcs=fit.wcs, refined=true, n_matches=fit.n, rms_arcsec=fit.rms_arcsec, offset_px=offset,
|
|
130
|
+
n_detections=length(detections))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
_vote_offset(det_xy, predicted, max_offset; bin=2.0) -> (dx, dy)
|
|
135
|
+
|
|
136
|
+
The global pixel offset (detected minus predicted) most detection/star
|
|
137
|
+
pairs agree on: every pair within `max_offset` votes for its own offset
|
|
138
|
+
in `bin`-pixel cells, and the winning cell's pairs are averaged. Random
|
|
139
|
+
pairings spread their votes thinly over the whole `max_offset` square;
|
|
140
|
+
true pairings all land in one cell, so this finds the header's error
|
|
141
|
+
without needing it to already be small.
|
|
142
|
+
"""
|
|
143
|
+
function _vote_offset(det_xy, predicted, max_offset::Real; bin::Real=2.0)
|
|
144
|
+
votes = Dict{Tuple{Int,Int},Vector{Tuple{Float64,Float64}}}()
|
|
145
|
+
for (px, py, _, _) in predicted, (dx_, dy_) in det_xy
|
|
146
|
+
ox, oy = dx_ - px, dy_ - py
|
|
147
|
+
(abs(ox) <= max_offset && abs(oy) <= max_offset) || continue
|
|
148
|
+
push!(get!(votes, (floor(Int, ox / bin), floor(Int, oy / bin)), Tuple{Float64,Float64}[]), (ox, oy))
|
|
149
|
+
end
|
|
150
|
+
isempty(votes) && return (0.0, 0.0)
|
|
151
|
+
# Count each cell together with its 8 neighbours, so a true offset
|
|
152
|
+
# sitting on a cell boundary isn't split across two cells.
|
|
153
|
+
neighbourhood(c) = (get(votes, (c[1] + a, c[2] + b), ()) for a in -1:1, b in -1:1)
|
|
154
|
+
best = argmax(c -> sum(length, neighbourhood(c)), collect(keys(votes)))
|
|
155
|
+
pairs = collect(Iterators.flatten(neighbourhood(best)))
|
|
156
|
+
return (median(first.(pairs)), median(last.(pairs)))
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
function _match_stars(det_xy, predicted, offset, match_radius::Real)
|
|
160
|
+
matches = Tuple{Float64,Float64,Float64,Float64}[] # (x, y, ra, dec)
|
|
161
|
+
used = Set{Int}()
|
|
162
|
+
for (px, py, ra, dec) in predicted
|
|
163
|
+
tx, ty = px + offset[1], py + offset[2]
|
|
164
|
+
best, best_dist = 0, Float64(match_radius)
|
|
165
|
+
for (i, (x, y)) in enumerate(det_xy)
|
|
166
|
+
d = hypot(x - tx, y - ty)
|
|
167
|
+
d <= best_dist && ((best, best_dist) = (i, d))
|
|
168
|
+
end
|
|
169
|
+
(best == 0 || best in used) && continue
|
|
170
|
+
push!(used, best)
|
|
171
|
+
push!(matches, (det_xy[best]..., ra, dec))
|
|
172
|
+
end
|
|
173
|
+
return matches
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
_gnomonic(ra, dec, crval) -> (xi, eta)
|
|
178
|
+
|
|
179
|
+
Standard (tangent-plane) coordinates of `(ra, dec)` about tangent point
|
|
180
|
+
`crval`, in degrees — FITS's intermediate world coordinates for a TAN
|
|
181
|
+
projection, so a linear fit of pixels to these *is* a TAN WCS solution.
|
|
182
|
+
"""
|
|
183
|
+
function _gnomonic(ra::Real, dec::Real, crval)
|
|
184
|
+
a0, d0 = crval
|
|
185
|
+
cosc = sind(d0) * sind(dec) + cosd(d0) * cosd(dec) * cosd(ra - a0)
|
|
186
|
+
xi = cosd(dec) * sind(ra - a0) / cosc
|
|
187
|
+
eta = (cosd(d0) * sind(dec) - sind(d0) * cosd(dec) * cosd(ra - a0)) / cosc
|
|
188
|
+
return rad2deg(xi), rad2deg(eta)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
function _fit_tan(matches, crval, clip_sigma::Real)
|
|
192
|
+
keep = trues(length(matches))
|
|
193
|
+
local a, b, rms
|
|
194
|
+
while true
|
|
195
|
+
m = matches[keep]
|
|
196
|
+
X = hcat(ones(length(m)), [p[1] for p in m], [p[2] for p in m])
|
|
197
|
+
std_coords = [_gnomonic(p[3], p[4], crval) for p in m]
|
|
198
|
+
a = X \ first.(std_coords)
|
|
199
|
+
b = X \ last.(std_coords)
|
|
200
|
+
all_X = hcat(ones(length(matches)), [p[1] for p in matches], [p[2] for p in matches])
|
|
201
|
+
all_std = [_gnomonic(p[3], p[4], crval) for p in matches]
|
|
202
|
+
resid = hypot.(all_X * a .- first.(all_std), all_X * b .- last.(all_std)) .* 3600
|
|
203
|
+
rms = sqrt(mean(resid[keep] .^ 2))
|
|
204
|
+
# Monotone: a rejected match never comes back, so each pass drops at
|
|
205
|
+
# least one or stops. Re-admitting them could oscillate forever on
|
|
206
|
+
# near-noiseless data, where residuals sit right at the threshold.
|
|
207
|
+
new_keep = keep .& (resid .<= max(clip_sigma * rms, 1e-3))
|
|
208
|
+
(new_keep == keep || count(new_keep) < 3) && break
|
|
209
|
+
keep = new_keep
|
|
210
|
+
end
|
|
211
|
+
cd = [a[2] a[3]; b[2] b[3]]
|
|
212
|
+
crpix = -(cd \ [a[1], b[1]])
|
|
213
|
+
wcs = WCSTransform(2; ctype=["RA---TAN", "DEC--TAN"], crval=[crval[1], crval[2]],
|
|
214
|
+
crpix=crpix, cdelt=[1.0, 1.0], pc=cd)
|
|
215
|
+
return (wcs=wcs, n=count(keep), rms_arcsec=rms)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
"""
|
|
219
|
+
_field_reference_stars(image, wcs, epoch_jd) -> Table
|
|
220
|
+
|
|
221
|
+
[`gaia_reference_stars`](@ref) covering all of `image`'s footprint
|
|
222
|
+
(through `wcs`), with a 10% margin for the header WCS's own error.
|
|
223
|
+
"""
|
|
224
|
+
function _field_reference_stars(image::AbstractMatrix, wcs::WCSTransform, epoch_jd::Real)
|
|
225
|
+
n1, n2 = size(image)
|
|
226
|
+
centre = pix_to_world(wcs, Float64[n2 / 2, n1 / 2])
|
|
227
|
+
corners = (pix_to_world(wcs, Float64[x, y]) for (x, y) in ((1, 1), (n2, 1), (1, n1), (n2, n1)))
|
|
228
|
+
radius = 1.1 * maximum(_angular_distance_arcsec(centre..., c...) for c in corners) / 3600
|
|
229
|
+
return gaia_reference_stars(centre[1], centre[2], radius; epoch_jd)
|
|
230
|
+
end
|