allclear 0.2.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.
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v4
18
+ with:
19
+ version: "latest"
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ run: uv python install ${{ matrix.python-version }}
22
+ - name: Install dependencies
23
+ run: uv sync --python ${{ matrix.python-version }}
24
+ - name: Run tests with coverage
25
+ run: uv run pytest allclear/tests/ -v --cov=allclear --cov-report=xml
26
+ - name: Upload coverage to Codecov
27
+ if: matrix.python-version == '3.12'
28
+ uses: codecov/codecov-action@v4
29
+ with:
30
+ files: coverage.xml
31
+ fail_ci_if_error: false
32
+ env:
33
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,220 @@
1
+ # Project data & outputs
2
+ example_images/
3
+ extr/
4
+ catalog/
5
+ /*.png
6
+ /*.gif
7
+ /*.jpg
8
+ /*.json
9
+ *.fits
10
+ .python-version
11
+ fit_distortion.py
12
+ reports/
13
+
14
+ # Byte-compiled / optimized / DLL files
15
+ __pycache__/
16
+ *.py[codz]
17
+ *$py.class
18
+
19
+ # C extensions
20
+ *.so
21
+
22
+ # Distribution / packaging
23
+ .Python
24
+ build/
25
+ develop-eggs/
26
+ dist/
27
+ downloads/
28
+ eggs/
29
+ .eggs/
30
+ lib/
31
+ lib64/
32
+ parts/
33
+ sdist/
34
+ var/
35
+ wheels/
36
+ share/python-wheels/
37
+ *.egg-info/
38
+ .installed.cfg
39
+ *.egg
40
+ MANIFEST
41
+
42
+ # PyInstaller
43
+ # Usually these files are written by a python script from a template
44
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
45
+ *.manifest
46
+ *.spec
47
+
48
+ # Installer logs
49
+ pip-log.txt
50
+ pip-delete-this-directory.txt
51
+
52
+ # Unit test / coverage reports
53
+ htmlcov/
54
+ .tox/
55
+ .nox/
56
+ .coverage
57
+ .coverage.*
58
+ .cache
59
+ nosetests.xml
60
+ coverage.xml
61
+ *.cover
62
+ *.py.cover
63
+ .hypothesis/
64
+ .pytest_cache/
65
+ cover/
66
+
67
+ # Translations
68
+ *.mo
69
+ *.pot
70
+
71
+ # Django stuff:
72
+ *.log
73
+ local_settings.py
74
+ db.sqlite3
75
+ db.sqlite3-journal
76
+
77
+ # Flask stuff:
78
+ instance/
79
+ .webassets-cache
80
+
81
+ # Scrapy stuff:
82
+ .scrapy
83
+
84
+ # Sphinx documentation
85
+ docs/_build/
86
+
87
+ # PyBuilder
88
+ .pybuilder/
89
+ target/
90
+
91
+ # Jupyter Notebook
92
+ .ipynb_checkpoints
93
+
94
+ # IPython
95
+ profile_default/
96
+ ipython_config.py
97
+
98
+ # pyenv
99
+ # For a library or package, you might want to ignore these files since the code is
100
+ # intended to run in multiple environments; otherwise, check them in:
101
+ # .python-version
102
+
103
+ # pipenv
104
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
105
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
106
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
107
+ # install all needed dependencies.
108
+ #Pipfile.lock
109
+
110
+ # UV
111
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
112
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
113
+ # commonly ignored for libraries.
114
+ #uv.lock
115
+
116
+ # poetry
117
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
118
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
119
+ # commonly ignored for libraries.
120
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
121
+ #poetry.lock
122
+ #poetry.toml
123
+
124
+ # pdm
125
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
126
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
127
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
128
+ #pdm.lock
129
+ #pdm.toml
130
+ .pdm-python
131
+ .pdm-build/
132
+
133
+ # pixi
134
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
135
+ #pixi.lock
136
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
137
+ # in the .venv directory. It is recommended not to include this directory in version control.
138
+ .pixi
139
+
140
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
141
+ __pypackages__/
142
+
143
+ # Celery stuff
144
+ celerybeat-schedule
145
+ celerybeat.pid
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ #.idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+
204
+ # Ruff stuff:
205
+ .ruff_cache/
206
+
207
+ # PyPI configuration file
208
+ .pypirc
209
+
210
+ # Cursor
211
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
212
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
213
+ # refer to https://docs.cursor.com/context/ignore-files
214
+ .cursorignore
215
+ .cursorindexingignore
216
+
217
+ # Marimo
218
+ marimo/_static/
219
+ marimo/_lsp/
220
+ __marimo__/
allclear-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zach Gazak
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,50 @@
1
+ # AllClear Makefile
2
+
3
+ .PHONY: help install install-dev test coverage badges build clean publish publish-test
4
+
5
+ PYTHON := uv run python
6
+
7
+ help:
8
+ @echo "Available targets:"
9
+ @echo " install - Install package"
10
+ @echo " install-dev - Install with dev dependencies"
11
+ @echo " test - Run tests"
12
+ @echo " coverage - Run tests with coverage + generate badges"
13
+ @echo " build - Build wheel and sdist"
14
+ @echo " clean - Remove build artifacts"
15
+ @echo " publish-test - Upload to TestPyPI"
16
+ @echo " publish - Upload to PyPI"
17
+
18
+ install:
19
+ uv pip install -e .
20
+
21
+ install-dev:
22
+ uv pip install -e ".[dev]"
23
+
24
+ test:
25
+ uv run pytest allclear/tests/ -v
26
+
27
+ coverage:
28
+ uv run pytest allclear/tests/ -v \
29
+ --junitxml=reports/junit/junit.xml \
30
+ --cov=allclear \
31
+ --cov-report=term-missing \
32
+ --cov-report=xml \
33
+ --cov-report=html
34
+ uv run genbadge tests -o tests.svg
35
+ uv run genbadge coverage -i coverage.xml -o coverage.svg
36
+ @echo "Coverage report: htmlcov/index.html"
37
+
38
+ build: clean
39
+ $(PYTHON) -m build
40
+
41
+ clean:
42
+ rm -rf build/ dist/ *.egg-info/
43
+ rm -rf .pytest_cache/ htmlcov/ .coverage coverage.xml reports/
44
+ find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
45
+
46
+ publish-test: build
47
+ uv run twine upload --repository testpypi dist/*
48
+
49
+ publish: build
50
+ uv run twine upload dist/*
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: allclear
3
+ Version: 0.2.0
4
+ Summary: All-sky camera cloud detection via stellar transmission mapping
5
+ Project-URL: Homepage, https://github.com/zgazak/allclear
6
+ Project-URL: Issues, https://github.com/zgazak/allclear/issues
7
+ Author-email: Zach <jzg@alum.mit.edu>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: all-sky,astronomy,cloud-detection,fisheye,photometry,transmission
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: astropy
22
+ Requires-Dist: matplotlib
23
+ Requires-Dist: numpy
24
+ Requires-Dist: photutils
25
+ Requires-Dist: scipy
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.4.0; extra == 'dev'
28
+ Requires-Dist: genbadge[coverage,tests]>=1.1.3; extra == 'dev'
29
+ Requires-Dist: pytest; extra == 'dev'
30
+ Requires-Dist: pytest-cov; extra == 'dev'
31
+ Requires-Dist: twine>=6.2.0; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # allclear
35
+
36
+ ![Tests](tests.svg) ![Coverage](coverage.svg)
37
+
38
+ All-sky camera cloud detection via stellar transmission mapping.
39
+
40
+ `allclear` takes FITS images from all-sky cameras, blind-solves the camera
41
+ geometry by matching detected stars against a catalog, and produces
42
+ full-hemisphere cloud transmission maps with annotated PNG output.
43
+
44
+ | Clear sky | Obscured sky |
45
+ | :--------------------------------: | :--------------------------------------: |
46
+ | ![Clear sky](docs/clear_blink.gif) | ![Obscured sky](docs/obscured_blink.gif) |
47
+
48
+ *Blink comparison: star identification (red circles = catalog, green crosshairs = matched) and transmission overlay (green = clear, red = cloudy).*
49
+
50
+ ## How it works
51
+
52
+ 1. **Detection** -- Background-subtracted star detection using DAOStarFinder
53
+ 2. **Catalog** -- Hipparcos/BSC5 bright star catalog (~9000 stars, V <= 6.5) projected to observer alt/az with atmospheric refraction and extinction
54
+ 3. **Blind solve** -- Hypothesis-and-verify pattern matching: for each bright (detection, catalog star) pair, hypothesize camera parameters, count matches. Best hypothesis seeds iterative refinement.
55
+ 4. **Camera model** -- Least-squares refinement of a fisheye camera model (center, focal length, boresight, roll, radial distortion) with automatic projection type selection
56
+ 5. **Guided matching** -- For each catalog star, project to pixel position and find the nearest bright peak. Sub-pixel centroiding. Avoids DAOStarFinder artifacts from domes/obstructions.
57
+ 6. **Transmission** -- Per-star photometric transmission from local-background-subtracted flux vs catalog magnitude, calibrated against a global clear-sky zeropoint. Unmatched catalog stars contribute zero transmission. Interpolated via RBF.
58
+
59
+ ## Installation
60
+
61
+ ```
62
+ pip install allclear
63
+ ```
64
+
65
+ Or with [uv](https://docs.astral.sh/uv/):
66
+
67
+ ```
68
+ uv add allclear
69
+ uv sync
70
+ ```
71
+
72
+ Requires Python >= 3.10. Dependencies: numpy, scipy, astropy, photutils, matplotlib.
73
+
74
+ ## Quick start
75
+
76
+ ### 1. Characterize camera geometry (one-time)
77
+
78
+ ```
79
+ allclear instrument-fit \
80
+ --frames your_sky_image.fits \
81
+ --lat 20.7458 --lon -156.4317 \
82
+ --output instrument_model.json
83
+ ```
84
+
85
+ This blind-solves the camera model and saves it as JSON, including a
86
+ photometric zeropoint calibrated from the (presumably clear) input frame.
87
+ Typically matches 400-550 stars at 2-3 pixel RMS.
88
+
89
+ Supports FITS, JPG, PNG, and TIFF. For non-FITS images, provide the
90
+ observation time with `--time`:
91
+
92
+ ```
93
+ allclear instrument-fit \
94
+ --frames sky_photo.jpg \
95
+ --lat 20.7458 --lon -156.4317 \
96
+ --time "2024-01-15 03:30:00-10:00" \
97
+ --output instrument_model.json
98
+ ```
99
+
100
+ ### 2. Process frames with known model (fast)
101
+
102
+ ```
103
+ allclear solve \
104
+ --frames "sky_images/*.fits" \
105
+ --model instrument_model.json
106
+ ```
107
+
108
+ For each frame, produces:
109
+ - `<stem>_solved.png` -- Annotated image with star matches (green crosshairs = matched, red circles = catalog)
110
+ - `<stem>_transmission.png` -- Same image with RdYlGn transmission overlay (green = clear, red = cloudy/obstructed)
111
+ - `<stem>_blink.gif` -- Animated blink between solved and transmission views
112
+
113
+ Use `--no-plot` to suppress image output. Use `--output-dir <dir>` to write to a specific directory.
114
+
115
+ ### 3. Quick sky check
116
+
117
+ ```
118
+ allclear check \
119
+ --frame sky_image.fits \
120
+ --model instrument_model.json \
121
+ --target-ra 83.63 --target-dec -5.39 --target-name "Orion Nebula"
122
+ ```
123
+
124
+ Reports overall clear fraction and transmission at a specific sky position.
125
+
126
+ ## Camera model
127
+
128
+ The fisheye projection model supports four lens types:
129
+
130
+ | Type | Formula |
131
+ | ------------- | --------------------- |
132
+ | Equidistant | r = f * theta |
133
+ | Equisolid | r = 2f * sin(theta/2) |
134
+ | Stereographic | r = 2f * tan(theta/2) |
135
+ | Orthographic | r = f * sin(theta) |
136
+
137
+ Parameters: optical center (cx, cy), boresight direction (az0, alt0),
138
+ roll angle (rho), focal length (f), and radial distortion coefficients (k1, k2).
139
+
140
+ The instrument model is saved as a JSON file with camera geometry,
141
+ site coordinates, detection settings, photometric zeropoint, and fit quality
142
+ metadata.
143
+
144
+ ## Transmission mapping
145
+
146
+ Transmission is measured on an **absolute scale**: the photometric zeropoint
147
+ from `instrument-fit` (clear sky) defines transmission = 1.0. Subsequent
148
+ `solve` frames measure dimming relative to that reference.
149
+
150
+ - Stars behind obstructions (domes, telescopes) are detected via
151
+ local-background contrast -- bright diffuse glow is distinguished from
152
+ point-source starlight
153
+ - Unmatched catalog stars (bright, in-frame, but no detectable point source)
154
+ contribute zero transmission to the interpolation
155
+ - If a solve frame has a *better* zeropoint than the model, a warning
156
+ suggests re-running `instrument-fit` with the clearer frame
157
+
158
+ ## Project structure
159
+
160
+ ```
161
+ allclear/
162
+ __init__.py # Package init
163
+ cli.py # Command-line interface (instrument-fit, solve, check)
164
+ strategies.py # Blind solve pipeline, pattern matching, guided refinement
165
+ solver.py # Fast solve with known model (guided matching + pointing refinement)
166
+ instrument.py # InstrumentModel dataclass + JSON persistence
167
+ projection.py # Fisheye camera model (4 projection types, sky<->pixel)
168
+ catalog.py # BrightStarCatalog (Hipparcos/BSC5, refraction, extinction)
169
+ detection.py # Star detection (Background2D + DAOStarFinder)
170
+ matching.py # KDTree 1-to-1 matching, triangle-hash blind matching
171
+ transmission.py # Per-star photometry, RBF interpolation, TransmissionMap
172
+ plotting.py # Annotated frame rendering, grid overlay, planets
173
+ synthetic.py # Synthetic frame generation for testing
174
+ utils.py # FITS I/O, coordinate math, extinction
175
+ data/ # Cached star catalog (ECSV)
176
+ tests/ # Test suite (32 tests)
177
+ ```
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ git clone https://github.com/zgazak/allclear.git
183
+ cd allclear
184
+ uv sync # install with dev dependencies
185
+ uv run pytest allclear/tests/ -v # run tests (32 tests)
186
+ ```
187
+
188
+ ## License
189
+
190
+ See [LICENSE](LICENSE).
@@ -0,0 +1,157 @@
1
+ # allclear
2
+
3
+ ![Tests](tests.svg) ![Coverage](coverage.svg)
4
+
5
+ All-sky camera cloud detection via stellar transmission mapping.
6
+
7
+ `allclear` takes FITS images from all-sky cameras, blind-solves the camera
8
+ geometry by matching detected stars against a catalog, and produces
9
+ full-hemisphere cloud transmission maps with annotated PNG output.
10
+
11
+ | Clear sky | Obscured sky |
12
+ | :--------------------------------: | :--------------------------------------: |
13
+ | ![Clear sky](docs/clear_blink.gif) | ![Obscured sky](docs/obscured_blink.gif) |
14
+
15
+ *Blink comparison: star identification (red circles = catalog, green crosshairs = matched) and transmission overlay (green = clear, red = cloudy).*
16
+
17
+ ## How it works
18
+
19
+ 1. **Detection** -- Background-subtracted star detection using DAOStarFinder
20
+ 2. **Catalog** -- Hipparcos/BSC5 bright star catalog (~9000 stars, V <= 6.5) projected to observer alt/az with atmospheric refraction and extinction
21
+ 3. **Blind solve** -- Hypothesis-and-verify pattern matching: for each bright (detection, catalog star) pair, hypothesize camera parameters, count matches. Best hypothesis seeds iterative refinement.
22
+ 4. **Camera model** -- Least-squares refinement of a fisheye camera model (center, focal length, boresight, roll, radial distortion) with automatic projection type selection
23
+ 5. **Guided matching** -- For each catalog star, project to pixel position and find the nearest bright peak. Sub-pixel centroiding. Avoids DAOStarFinder artifacts from domes/obstructions.
24
+ 6. **Transmission** -- Per-star photometric transmission from local-background-subtracted flux vs catalog magnitude, calibrated against a global clear-sky zeropoint. Unmatched catalog stars contribute zero transmission. Interpolated via RBF.
25
+
26
+ ## Installation
27
+
28
+ ```
29
+ pip install allclear
30
+ ```
31
+
32
+ Or with [uv](https://docs.astral.sh/uv/):
33
+
34
+ ```
35
+ uv add allclear
36
+ uv sync
37
+ ```
38
+
39
+ Requires Python >= 3.10. Dependencies: numpy, scipy, astropy, photutils, matplotlib.
40
+
41
+ ## Quick start
42
+
43
+ ### 1. Characterize camera geometry (one-time)
44
+
45
+ ```
46
+ allclear instrument-fit \
47
+ --frames your_sky_image.fits \
48
+ --lat 20.7458 --lon -156.4317 \
49
+ --output instrument_model.json
50
+ ```
51
+
52
+ This blind-solves the camera model and saves it as JSON, including a
53
+ photometric zeropoint calibrated from the (presumably clear) input frame.
54
+ Typically matches 400-550 stars at 2-3 pixel RMS.
55
+
56
+ Supports FITS, JPG, PNG, and TIFF. For non-FITS images, provide the
57
+ observation time with `--time`:
58
+
59
+ ```
60
+ allclear instrument-fit \
61
+ --frames sky_photo.jpg \
62
+ --lat 20.7458 --lon -156.4317 \
63
+ --time "2024-01-15 03:30:00-10:00" \
64
+ --output instrument_model.json
65
+ ```
66
+
67
+ ### 2. Process frames with known model (fast)
68
+
69
+ ```
70
+ allclear solve \
71
+ --frames "sky_images/*.fits" \
72
+ --model instrument_model.json
73
+ ```
74
+
75
+ For each frame, produces:
76
+ - `<stem>_solved.png` -- Annotated image with star matches (green crosshairs = matched, red circles = catalog)
77
+ - `<stem>_transmission.png` -- Same image with RdYlGn transmission overlay (green = clear, red = cloudy/obstructed)
78
+ - `<stem>_blink.gif` -- Animated blink between solved and transmission views
79
+
80
+ Use `--no-plot` to suppress image output. Use `--output-dir <dir>` to write to a specific directory.
81
+
82
+ ### 3. Quick sky check
83
+
84
+ ```
85
+ allclear check \
86
+ --frame sky_image.fits \
87
+ --model instrument_model.json \
88
+ --target-ra 83.63 --target-dec -5.39 --target-name "Orion Nebula"
89
+ ```
90
+
91
+ Reports overall clear fraction and transmission at a specific sky position.
92
+
93
+ ## Camera model
94
+
95
+ The fisheye projection model supports four lens types:
96
+
97
+ | Type | Formula |
98
+ | ------------- | --------------------- |
99
+ | Equidistant | r = f * theta |
100
+ | Equisolid | r = 2f * sin(theta/2) |
101
+ | Stereographic | r = 2f * tan(theta/2) |
102
+ | Orthographic | r = f * sin(theta) |
103
+
104
+ Parameters: optical center (cx, cy), boresight direction (az0, alt0),
105
+ roll angle (rho), focal length (f), and radial distortion coefficients (k1, k2).
106
+
107
+ The instrument model is saved as a JSON file with camera geometry,
108
+ site coordinates, detection settings, photometric zeropoint, and fit quality
109
+ metadata.
110
+
111
+ ## Transmission mapping
112
+
113
+ Transmission is measured on an **absolute scale**: the photometric zeropoint
114
+ from `instrument-fit` (clear sky) defines transmission = 1.0. Subsequent
115
+ `solve` frames measure dimming relative to that reference.
116
+
117
+ - Stars behind obstructions (domes, telescopes) are detected via
118
+ local-background contrast -- bright diffuse glow is distinguished from
119
+ point-source starlight
120
+ - Unmatched catalog stars (bright, in-frame, but no detectable point source)
121
+ contribute zero transmission to the interpolation
122
+ - If a solve frame has a *better* zeropoint than the model, a warning
123
+ suggests re-running `instrument-fit` with the clearer frame
124
+
125
+ ## Project structure
126
+
127
+ ```
128
+ allclear/
129
+ __init__.py # Package init
130
+ cli.py # Command-line interface (instrument-fit, solve, check)
131
+ strategies.py # Blind solve pipeline, pattern matching, guided refinement
132
+ solver.py # Fast solve with known model (guided matching + pointing refinement)
133
+ instrument.py # InstrumentModel dataclass + JSON persistence
134
+ projection.py # Fisheye camera model (4 projection types, sky<->pixel)
135
+ catalog.py # BrightStarCatalog (Hipparcos/BSC5, refraction, extinction)
136
+ detection.py # Star detection (Background2D + DAOStarFinder)
137
+ matching.py # KDTree 1-to-1 matching, triangle-hash blind matching
138
+ transmission.py # Per-star photometry, RBF interpolation, TransmissionMap
139
+ plotting.py # Annotated frame rendering, grid overlay, planets
140
+ synthetic.py # Synthetic frame generation for testing
141
+ utils.py # FITS I/O, coordinate math, extinction
142
+ data/ # Cached star catalog (ECSV)
143
+ tests/ # Test suite (32 tests)
144
+ ```
145
+
146
+ ## Development
147
+
148
+ ```bash
149
+ git clone https://github.com/zgazak/allclear.git
150
+ cd allclear
151
+ uv sync # install with dev dependencies
152
+ uv run pytest allclear/tests/ -v # run tests (32 tests)
153
+ ```
154
+
155
+ ## License
156
+
157
+ See [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """AllClear — all-sky camera cloud detection via star matching."""
2
+
3
+ __version__ = "0.2.0"