unisky 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.
- unisky-0.2.0/.gitignore +21 -0
- unisky-0.2.0/CHANGELOG.md +386 -0
- unisky-0.2.0/LICENSE +29 -0
- unisky-0.2.0/PKG-INFO +653 -0
- unisky-0.2.0/README.md +616 -0
- unisky-0.2.0/docs/alignment.md +347 -0
- unisky-0.2.0/docs/composites.md +289 -0
- unisky-0.2.0/docs/deploy.md +1047 -0
- unisky-0.2.0/docs/frontend-bundle.md +126 -0
- unisky-0.2.0/docs/incremental.md +696 -0
- unisky-0.2.0/docs/lensing-normalization.md +79 -0
- unisky-0.2.0/docs/prerendered.md +326 -0
- unisky-0.2.0/docs/remote-data.md +1355 -0
- unisky-0.2.0/docs/superpowers/plans/2026-08-20-anymap-api.md +1953 -0
- unisky-0.2.0/docs/superpowers/plans/2026-08-20-anymap-frontend.md +4625 -0
- unisky-0.2.0/docs/superpowers/plans/2026-08-20-anymap-integration.md +1891 -0
- unisky-0.2.0/docs/superpowers/plans/2026-08-20-anymap-python-core.md +3321 -0
- unisky-0.2.0/docs/superpowers/plans/2026-08-20-unisky-interfaces.md +1152 -0
- unisky-0.2.0/docs/superpowers/specs/2026-08-20-anymap-design.md +327 -0
- unisky-0.2.0/docs/uploads.md +304 -0
- unisky-0.2.0/examples/rxcj2211.yaml +677 -0
- unisky-0.2.0/examples/rxcj2211_core.yaml +212 -0
- unisky-0.2.0/frontend/e2e/viewer.spec.ts +739 -0
- unisky-0.2.0/frontend/index.html +66 -0
- unisky-0.2.0/frontend/package-lock.json +2529 -0
- unisky-0.2.0/frontend/package.json +25 -0
- unisky-0.2.0/frontend/playwright.config.ts +66 -0
- unisky-0.2.0/frontend/public/apple-touch-icon.png +0 -0
- unisky-0.2.0/frontend/public/favicon-16.png +0 -0
- unisky-0.2.0/frontend/public/favicon-32.png +0 -0
- unisky-0.2.0/frontend/public/favicon-48.png +0 -0
- unisky-0.2.0/frontend/public/favicon.ico +0 -0
- unisky-0.2.0/frontend/public/favicon.svg +15 -0
- unisky-0.2.0/frontend/src/api.ts +170 -0
- unisky-0.2.0/frontend/src/capabilities.ts +123 -0
- unisky-0.2.0/frontend/src/catalog.ts +416 -0
- unisky-0.2.0/frontend/src/colormaps.json +1 -0
- unisky-0.2.0/frontend/src/colormaps.ts +36 -0
- unisky-0.2.0/frontend/src/crs.ts +105 -0
- unisky-0.2.0/frontend/src/datatiles.ts +902 -0
- unisky-0.2.0/frontend/src/main.ts +862 -0
- unisky-0.2.0/frontend/src/manifest.ts +51 -0
- unisky-0.2.0/frontend/src/permalink.ts +390 -0
- unisky-0.2.0/frontend/src/rgb.ts +760 -0
- unisky-0.2.0/frontend/src/state.ts +1456 -0
- unisky-0.2.0/frontend/src/stretch.ts +187 -0
- unisky-0.2.0/frontend/src/styles.css +1364 -0
- unisky-0.2.0/frontend/src/types.ts +463 -0
- unisky-0.2.0/frontend/src/ui/about.ts +0 -0
- unisky-0.2.0/frontend/src/ui/contrast-drag.css +64 -0
- unisky-0.2.0/frontend/src/ui/contrast-drag.ts +736 -0
- unisky-0.2.0/frontend/src/ui/coords.ts +55 -0
- unisky-0.2.0/frontend/src/ui/cutout-tool.ts +536 -0
- unisky-0.2.0/frontend/src/ui/dock.ts +313 -0
- unisky-0.2.0/frontend/src/ui/goto.ts +79 -0
- unisky-0.2.0/frontend/src/ui/layer-tree.ts +1287 -0
- unisky-0.2.0/frontend/src/ui/layers-panel.ts +207 -0
- unisky-0.2.0/frontend/src/ui/lens-panel.ts +654 -0
- unisky-0.2.0/frontend/src/ui/menubar.ts +578 -0
- unisky-0.2.0/frontend/src/ui/mode-banner.ts +188 -0
- unisky-0.2.0/frontend/src/ui/popup.ts +291 -0
- unisky-0.2.0/frontend/src/ui/rgb-panel.ts +738 -0
- unisky-0.2.0/frontend/src/ui/scale-bar.ts +233 -0
- unisky-0.2.0/frontend/src/ui/stretch-panel.ts +205 -0
- unisky-0.2.0/frontend/src/ui/toast.ts +51 -0
- unisky-0.2.0/frontend/src/ui/upload-panel.ts +679 -0
- unisky-0.2.0/frontend/src/ui/window.ts +870 -0
- unisky-0.2.0/frontend/src/ui/zoom-slider.ts +211 -0
- unisky-0.2.0/frontend/src/upload.ts +1561 -0
- unisky-0.2.0/frontend/src/vector.ts +350 -0
- unisky-0.2.0/frontend/src/wcs.ts +240 -0
- unisky-0.2.0/frontend/src/zoom.ts +209 -0
- unisky-0.2.0/frontend/tests/about.test.ts +260 -0
- unisky-0.2.0/frontend/tests/busy.test.ts +59 -0
- unisky-0.2.0/frontend/tests/capabilities.test.ts +159 -0
- unisky-0.2.0/frontend/tests/catalog.test.ts +820 -0
- unisky-0.2.0/frontend/tests/contrast-drag.test.ts +860 -0
- unisky-0.2.0/frontend/tests/coords.test.ts +110 -0
- unisky-0.2.0/frontend/tests/crs.test.ts +90 -0
- unisky-0.2.0/frontend/tests/cutout-tool.test.ts +410 -0
- unisky-0.2.0/frontend/tests/datatiles.test.ts +550 -0
- unisky-0.2.0/frontend/tests/deployment.test.ts +673 -0
- unisky-0.2.0/frontend/tests/dock.test.ts +808 -0
- unisky-0.2.0/frontend/tests/fixtures/wcs_vectors.json +2235 -0
- unisky-0.2.0/frontend/tests/goto.test.ts +113 -0
- unisky-0.2.0/frontend/tests/layer-tree.test.ts +905 -0
- unisky-0.2.0/frontend/tests/layers-panel.test.ts +182 -0
- unisky-0.2.0/frontend/tests/lens-panel.test.ts +661 -0
- unisky-0.2.0/frontend/tests/main-wiring.test.ts +835 -0
- unisky-0.2.0/frontend/tests/manifest.test.ts +66 -0
- unisky-0.2.0/frontend/tests/menubar.test.ts +451 -0
- unisky-0.2.0/frontend/tests/mode-banner.test.ts +208 -0
- unisky-0.2.0/frontend/tests/node-fs.d.ts +36 -0
- unisky-0.2.0/frontend/tests/permalink.test.ts +433 -0
- unisky-0.2.0/frontend/tests/popup.test.ts +532 -0
- unisky-0.2.0/frontend/tests/rgb-panel.test.ts +725 -0
- unisky-0.2.0/frontend/tests/rgb.test.ts +677 -0
- unisky-0.2.0/frontend/tests/scale-bar.test.ts +253 -0
- unisky-0.2.0/frontend/tests/shell.test.ts +31 -0
- unisky-0.2.0/frontend/tests/state.test.ts +1385 -0
- unisky-0.2.0/frontend/tests/stretch-panel.test.ts +171 -0
- unisky-0.2.0/frontend/tests/stretch.test.ts +222 -0
- unisky-0.2.0/frontend/tests/theme.test.ts +365 -0
- unisky-0.2.0/frontend/tests/toast.test.ts +85 -0
- unisky-0.2.0/frontend/tests/types.test.ts +148 -0
- unisky-0.2.0/frontend/tests/upload.test.ts +1299 -0
- unisky-0.2.0/frontend/tests/vector.test.ts +443 -0
- unisky-0.2.0/frontend/tests/wcs.test.ts +207 -0
- unisky-0.2.0/frontend/tests/window.test.ts +564 -0
- unisky-0.2.0/frontend/tests/zoom-slider.test.ts +324 -0
- unisky-0.2.0/frontend/tests/zoom.test.ts +287 -0
- unisky-0.2.0/frontend/tsconfig.json +16 -0
- unisky-0.2.0/frontend/vite.config.ts +29 -0
- unisky-0.2.0/justfile +78 -0
- unisky-0.2.0/pyproject.toml +101 -0
- unisky-0.2.0/scripts/dev_build_synthetic.py +195 -0
- unisky-0.2.0/scripts/gen_colormaps.py +44 -0
- unisky-0.2.0/scripts/gen_wcs_vectors.py +94 -0
- unisky-0.2.0/scripts/make_favicon.py +68 -0
- unisky-0.2.0/scripts/make_test_field.py +336 -0
- unisky-0.2.0/src/unisky/__init__.py +6 -0
- unisky-0.2.0/src/unisky/align.py +737 -0
- unisky-0.2.0/src/unisky/build.py +3147 -0
- unisky-0.2.0/src/unisky/cli.py +553 -0
- unisky-0.2.0/src/unisky/config.py +2089 -0
- unisky-0.2.0/src/unisky/encoding.py +102 -0
- unisky-0.2.0/src/unisky/grid.py +388 -0
- unisky-0.2.0/src/unisky/layers/__init__.py +0 -0
- unisky-0.2.0/src/unisky/layers/catalog.py +411 -0
- unisky-0.2.0/src/unisky/layers/composite.py +417 -0
- unisky-0.2.0/src/unisky/layers/contour.py +61 -0
- unisky-0.2.0/src/unisky/layers/footprint.py +66 -0
- unisky-0.2.0/src/unisky/layers/image.py +121 -0
- unisky-0.2.0/src/unisky/layers/lensmodel.py +203 -0
- unisky-0.2.0/src/unisky/layers/prerendered.py +491 -0
- unisky-0.2.0/src/unisky/layers/region.py +138 -0
- unisky-0.2.0/src/unisky/lensing.py +464 -0
- unisky-0.2.0/src/unisky/manifest.py +461 -0
- unisky-0.2.0/src/unisky/progress.py +1066 -0
- unisky-0.2.0/src/unisky/remote.py +954 -0
- unisky-0.2.0/src/unisky/server/__init__.py +1 -0
- unisky-0.2.0/src/unisky/server/app.py +290 -0
- unisky-0.2.0/src/unisky/server/cutout.py +361 -0
- unisky-0.2.0/src/unisky/server/lens_api.py +107 -0
- unisky-0.2.0/src/unisky/sources/__init__.py +116 -0
- unisky-0.2.0/src/unisky/sources/footprints.py +1245 -0
- unisky-0.2.0/src/unisky/sources/hips.py +1036 -0
- unisky-0.2.0/src/unisky/sources/legacysurvey.py +803 -0
- unisky-0.2.0/src/unisky/static/apple-touch-icon.png +0 -0
- unisky-0.2.0/src/unisky/static/assets/index-BpPCJ8ql.js +344 -0
- unisky-0.2.0/src/unisky/static/assets/index-CAJd-K9y.css +1 -0
- unisky-0.2.0/src/unisky/static/bundle-source.json +70 -0
- unisky-0.2.0/src/unisky/static/favicon-16.png +0 -0
- unisky-0.2.0/src/unisky/static/favicon-32.png +0 -0
- unisky-0.2.0/src/unisky/static/favicon-48.png +0 -0
- unisky-0.2.0/src/unisky/static/favicon.ico +0 -0
- unisky-0.2.0/src/unisky/static/favicon.svg +15 -0
- unisky-0.2.0/src/unisky/static/index.html +67 -0
- unisky-0.2.0/src/unisky/tiler.py +678 -0
- unisky-0.2.0/tests/__init__.py +0 -0
- unisky-0.2.0/tests/conftest.py +332 -0
- unisky-0.2.0/tests/fixtures/all_layer_types/config.yaml +244 -0
- unisky-0.2.0/tests/fixtures/all_layer_types/rgb.png +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/alma_clean.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/alma_pbcor.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/config.yaml +83 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/jwst_f090w.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/jwst_f200w.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/jwst_f444w.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/lens_alpha_x.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/lens_alpha_y.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/lens_gamma1.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/lens_gamma2.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/lens_kappa.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/lens_psi.fits +0 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/mulimage.reg +149 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/mulimage.txt +147 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/phot_cut.fits +7 -0
- unisky-0.2.0/tests/fixtures/rxcj2211_tiny/photz_cut.ecsv +70 -0
- unisky-0.2.0/tests/fixtures/wcs_vectors.json +2235 -0
- unisky-0.2.0/tests/test_align.py +1169 -0
- unisky-0.2.0/tests/test_build.py +1658 -0
- unisky-0.2.0/tests/test_bundle_freshness.py +293 -0
- unisky-0.2.0/tests/test_catalog.py +683 -0
- unisky-0.2.0/tests/test_cli.py +1234 -0
- unisky-0.2.0/tests/test_composite.py +1321 -0
- unisky-0.2.0/tests/test_config.py +1132 -0
- unisky-0.2.0/tests/test_contour.py +54 -0
- unisky-0.2.0/tests/test_cutout_api.py +604 -0
- unisky-0.2.0/tests/test_encoding.py +75 -0
- unisky-0.2.0/tests/test_example_config.py +793 -0
- unisky-0.2.0/tests/test_footprints.py +1337 -0
- unisky-0.2.0/tests/test_grid.py +544 -0
- unisky-0.2.0/tests/test_hips.py +1012 -0
- unisky-0.2.0/tests/test_image.py +138 -0
- unisky-0.2.0/tests/test_incremental.py +2270 -0
- unisky-0.2.0/tests/test_legacysurvey.py +956 -0
- unisky-0.2.0/tests/test_lens_api.py +235 -0
- unisky-0.2.0/tests/test_lensing_critcurve.py +82 -0
- unisky-0.2.0/tests/test_lensing_delay.py +175 -0
- unisky-0.2.0/tests/test_lensing_findimg.py +139 -0
- unisky-0.2.0/tests/test_lensing_point.py +112 -0
- unisky-0.2.0/tests/test_lensmodel.py +149 -0
- unisky-0.2.0/tests/test_make_test_field.py +197 -0
- unisky-0.2.0/tests/test_manifest.py +116 -0
- unisky-0.2.0/tests/test_prerendered.py +919 -0
- unisky-0.2.0/tests/test_progress.py +938 -0
- unisky-0.2.0/tests/test_realdata_lensing.py +419 -0
- unisky-0.2.0/tests/test_region.py +82 -0
- unisky-0.2.0/tests/test_remote.py +1225 -0
- unisky-0.2.0/tests/test_server_app.py +671 -0
- unisky-0.2.0/tests/test_static_assets.py +172 -0
- unisky-0.2.0/tests/test_tiler.py +560 -0
unisky-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.egg-info/
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
dist/
|
|
5
|
+
build*/
|
|
6
|
+
node_modules/
|
|
7
|
+
frontend/.vite/
|
|
8
|
+
.e2e-build/
|
|
9
|
+
.DS_Store
|
|
10
|
+
.venv/
|
|
11
|
+
tests/fixtures/rxcj2211_mini/
|
|
12
|
+
frontend/test-results/
|
|
13
|
+
frontend/playwright-report/
|
|
14
|
+
# page snapshots the Playwright MCP server drops beside the cwd
|
|
15
|
+
.playwright-mcp/
|
|
16
|
+
.history
|
|
17
|
+
|
|
18
|
+
# wrangler caches the Cloudflare account it authenticated as; never publishable
|
|
19
|
+
.wrangler/
|
|
20
|
+
# agent worktrees: a whole second copy of the tree, never publishable
|
|
21
|
+
.claude/worktrees/
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to UniSky are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [0.2.0] - 2026-08-23
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **The project is now called UniSky.** It was AnyMap; that name is taken on
|
|
11
|
+
PyPI. The rename is complete rather than aliased — there is no compatibility
|
|
12
|
+
shim anywhere — so an existing checkout, cache and built site each need one
|
|
13
|
+
action:
|
|
14
|
+
|
|
15
|
+
| was | is |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `pip install anymap`, `import anymap` | `pip install unisky`, `import unisky` |
|
|
18
|
+
| `anymap build` / `serve` / `add` | `unisky build` / `serve` / `add` |
|
|
19
|
+
| `$ANYMAP_CACHE`, `$ANYMAP_OFFLINE`, `$ANYMAP_SCRATCH` | `$UNISKY_CACHE`, `$UNISKY_OFFLINE`, `$UNISKY_SCRATCH` |
|
|
20
|
+
| `manifest.json` key `anymap_version` | `unisky_version` |
|
|
21
|
+
| catalog overlay marker `"AnyMapCatalog"` | `"UniSkyCatalog"` |
|
|
22
|
+
| `localStorage` keys `anymap.*` | `unisky.*` |
|
|
23
|
+
|
|
24
|
+
- **Reinstall the console script.** The old `anymap` entry point belongs to
|
|
25
|
+
the old distribution: `pip uninstall -y anymap && pip install -e ".[dev]"`.
|
|
26
|
+
Without `-y`, `pip uninstall` stops to ask, and outside an interactive
|
|
27
|
+
terminal it dies on the prompt and the `&&` never runs.
|
|
28
|
+
- **MERGE the download cache — do not let it be re-fetched, and do not
|
|
29
|
+
`mv` it.** It holds real archive downloads (DESI cutouts, HiPS tiles, TAP
|
|
30
|
+
answers), and the code does not look at the old path:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# keeps whichever copy of a file is newer, and says what it moved;
|
|
34
|
+
# correct whether or not ~/.cache/unisky already exists
|
|
35
|
+
rsync -a --update --itemize-changes ~/.cache/anymap/ ~/.cache/unisky/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`mv ~/.cache/anymap ~/.cache/unisky` — which earlier drafts of this entry
|
|
39
|
+
and of the README recommended — is correct only while the destination
|
|
40
|
+
does not exist. Once it does, and it does from the first `unisky build`
|
|
41
|
+
onwards, `mv` nests the old cache at `~/.cache/unisky/anymap/`, where
|
|
42
|
+
`cache_root()` never looks: it strands exactly the data it was meant to
|
|
43
|
+
save, and exits 0. Both trailing slashes above are load-bearing.
|
|
44
|
+
|
|
45
|
+
Nothing inside it changes; the cache key is the canonical request URL.
|
|
46
|
+
See [docs/remote-data.md](docs/remote-data.md).
|
|
47
|
+
- **Rebuild built sites.** `manifest.json` carries `unisky_version` and
|
|
48
|
+
nothing reads the old key, so `unisky serve` and `unisky add` refuse a
|
|
49
|
+
pre-rename site with a message that names the rebuild, and so does the
|
|
50
|
+
viewer. Rebuild with `unisky build <config.yaml> -o <site>`; configs
|
|
51
|
+
themselves are unchanged apart from any `$ANYMAP_*` they mention.
|
|
52
|
+
- Saved viewer state in the browser (theme, window positions) lives under
|
|
53
|
+
renamed `localStorage` keys and starts from the defaults once.
|
|
54
|
+
|
|
55
|
+
- **A broken config is now a message for a person, not a traceback.** A YAML
|
|
56
|
+
syntax error is reported with the line, the column, the offending source
|
|
57
|
+
line and a caret under it. A config that parses but does not validate is
|
|
58
|
+
refused with **every** problem at once, each carrying the line it is on, the
|
|
59
|
+
field path, the layer's `id`, and the nearest known name when there is one:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Error: config.yaml is not a valid UniSky config; 2 problems.
|
|
63
|
+
|
|
64
|
+
the config file:
|
|
65
|
+
/home/you/fields/config.yaml
|
|
66
|
+
|
|
67
|
+
what to do:
|
|
68
|
+
- line 31, layers[3].channels.g (layer 'rgb'): term 'half*f200w' has
|
|
69
|
+
no numeric weight: 'half' is not a number. Write it as
|
|
70
|
+
'<weight>*<layer id>', for example '0.5*f322w2'
|
|
71
|
+
- line 32, layers[3].lupton.q (layer 'rgb'): unknown key 'q' - did you
|
|
72
|
+
mean 'Q'?
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A file the config names but that cannot be read is caught the same way, in
|
|
76
|
+
one pre-flight pass before any work starts — every missing path at once,
|
|
77
|
+
resolved and printed in full, with nothing built and nothing written.
|
|
78
|
+
`--traceback` still prints the Python one underneath, and a failure that
|
|
79
|
+
looks like a bug in UniSky prints one anyway.
|
|
80
|
+
|
|
81
|
+
- **Config errors, unreadable files and archive failures are all laid out the
|
|
82
|
+
same way**: what happened, then the file, URL or directory it happened to,
|
|
83
|
+
then what to do about it — hard-wrapped to a fixed 72 columns (not
|
|
84
|
+
`$COLUMNS`, so the text reads the same wherever it lands), except that a
|
|
85
|
+
path, URL or command is left on its own line however long it is, because
|
|
86
|
+
wrapping one breaks copy-paste. It replaces the 1096-character single line
|
|
87
|
+
one of them used to be. A test reads every refusal in the package out of the
|
|
88
|
+
source and holds it to that layout, so one written tomorrow is covered the
|
|
89
|
+
moment it is written.
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- **`unisky add` and `unisky remove`: one more layer into a site that is
|
|
94
|
+
already built, and one back out.** A full-field build is minutes and
|
|
95
|
+
gigabytes, and a region file that turns up afterwards should not cost that
|
|
96
|
+
twice.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
unisky add config.yaml -o build/ lrd_msa # build ONE more layer in place
|
|
100
|
+
unisky remove -o build/ lrd_msa # ...and take it back out
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`add` builds only the layers you name, splices them into `manifest.json` in
|
|
104
|
+
**config order**, and leaves every other layer's tiles byte for byte as they
|
|
105
|
+
were. `remove` is the inverse and takes **no config at all**: what a layer
|
|
106
|
+
owns, and which composites are rendered from it, are read from the site's
|
|
107
|
+
own manifest — so a layer can leave the site before, after, or instead of
|
|
108
|
+
leaving the config. Both refuse rather than corrupt a site (a config that no
|
|
109
|
+
longer describes its display grid; a layer that misses the grid; a channel
|
|
110
|
+
whose composite would be left showing pixels it no longer has; the last
|
|
111
|
+
image layer), and `--dry-run` runs every check without writing a byte.
|
|
112
|
+
|
|
113
|
+
**The display grid never moves.** It is derived once, when the site is
|
|
114
|
+
built, from the union of every image layer's footprint, and every tile on
|
|
115
|
+
disk is addressed by it — so `add` reads the grid out of `manifest.json` and
|
|
116
|
+
reuses it exactly rather than re-derive it, and `remove` leaves it as wide
|
|
117
|
+
as it was even when the widest layer goes. Only `unisky build` derives a
|
|
118
|
+
grid. Remove a layer, add it straight back, and the site is the same bytes.
|
|
119
|
+
See [docs/incremental.md](docs/incremental.md).
|
|
120
|
+
|
|
121
|
+
- **A site now says what its DEPLOYMENT can offer, so one build can feed two.**
|
|
122
|
+
`manifest.json` grows `serve.cutout` and `serve.lensing`, and `unisky build`
|
|
123
|
+
grows three flags that set them without the config being edited between the
|
|
124
|
+
full internal site and the copy going somewhere poorer:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
unisky build config.yaml -o site/ # both on: the full server
|
|
128
|
+
unisky build config.yaml -o site/ --no-cutout # has the folder, not the data
|
|
129
|
+
unisky build config.yaml -o site/ --static # --no-cutout --no-lensing
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Two switches and not one**, because the two server-side features do not
|
|
133
|
+
need the same machine. A cutout re-opens the ORIGINAL FITS at request time,
|
|
134
|
+
by the absolute path in the layer's `native` block, so it needs the dataset
|
|
135
|
+
on the serving host — often terabytes that were never in the build folder.
|
|
136
|
+
The lensing endpoints read only the `.npy` grids the build already wrote
|
|
137
|
+
*inside* the folder, so they need a Python process and nothing else. A small
|
|
138
|
+
server holding the build and none of the data behind it can therefore do one
|
|
139
|
+
and not the other, and one switch could not have said that. Both default to
|
|
140
|
+
**true**, and a manifest carrying neither key — every site built before this
|
|
141
|
+
round — keeps meaning "everything, as before".
|
|
142
|
+
|
|
143
|
+
The flags are **negative only**: the config's `serve:` block is the ceiling,
|
|
144
|
+
and a build of it may be poorer, never richer.
|
|
145
|
+
|
|
146
|
+
- **`unisky capabilities <site>`: the two booleans, on a site already built.**
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
unisky capabilities site/ # with no flags: what does it offer?
|
|
150
|
+
unisky capabilities site/ --static # about to be uploaded to object storage
|
|
151
|
+
unisky capabilities site/ --lensing # ...and back up again, a month later
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
No config, no rebuild, and not one tile read or written: `manifest.json` is
|
|
155
|
+
republished by the same same-directory temp plus atomic rename that `add` and
|
|
156
|
+
`remove` use, so this is as cheap on a 300 GB site as on an empty one and an
|
|
157
|
+
interrupted run leaves the site exactly as it was. A new subcommand rather
|
|
158
|
+
than a flag, because every existing command needs something this does not (a
|
|
159
|
+
config, forty minutes, artifacts to delete, a process that outlives nothing),
|
|
160
|
+
and a **noun** because with no flags it changes nothing and reports what the
|
|
161
|
+
site offers — the question anyone asks before pushing a folder somewhere.
|
|
162
|
+
|
|
163
|
+
Here, and only here, a capability may go back **up**: this is the site's
|
|
164
|
+
declaration being written rather than read, and the way back from a flag
|
|
165
|
+
typed at the wrong directory must not be a second forty-minute build. Putting
|
|
166
|
+
cutouts back checks that the FITS this site names are readable here and
|
|
167
|
+
**warns rather than refuses** — a deployment folder is usually prepared
|
|
168
|
+
somewhere other than where it will be served.
|
|
169
|
+
|
|
170
|
+
A `--static` build still ships `lens/**` on purpose: it is what lets
|
|
171
|
+
`--lensing` turn lensing back on later without rebuilding, and it makes a
|
|
172
|
+
flipped site **byte-identical** to one rebuilt with the same shape, manifest
|
|
173
|
+
included.
|
|
174
|
+
|
|
175
|
+
- **Everything else agrees with the manifest, and only ever downward.**
|
|
176
|
+
`unisky serve` mounts each API router only when its flag is true; what takes
|
|
177
|
+
a router's place is a short **404** naming the flag — 404 and not 501 or 503,
|
|
178
|
+
because the object-storage copy of the same folder answers 404 and nothing
|
|
179
|
+
can change that, so one status means one thing and a client needs one code
|
|
180
|
+
path instead of two. `unisky serve --no-cutout` / `--no-lensing` / `--static`
|
|
181
|
+
lower a full build for one process and cannot raise one — there is no
|
|
182
|
+
`--cutout` to type — and a lowered process serves a lowered `manifest.json`
|
|
183
|
+
too, since the viewer decides what to offer from that document. `unisky add`
|
|
184
|
+
re-derives the block from the config like everything else and then **floors
|
|
185
|
+
it** with what the site already declares, saying so when the floor bites: a
|
|
186
|
+
rebuilt layer is not a reason for the FITS to have arrived on the server.
|
|
187
|
+
|
|
188
|
+
- **In the viewer, a feature the deployment cannot serve is ABSENT, not greyed
|
|
189
|
+
out.** `src/capabilities.ts` is the one place the two booleans are read, and
|
|
190
|
+
only a literal `false` takes a feature away — a missing block, a null, a
|
|
191
|
+
string left by a hand-edit all read as "available", because that is what they
|
|
192
|
+
have always meant. What goes with it is every way to ask: no panel, no dock
|
|
193
|
+
button, no remembered "was open last visit" (all three come from one
|
|
194
|
+
`registerWindow` that is never called), no menu item, and no accelerator in
|
|
195
|
+
the key map — *unbound* rather than bound to a no-op, so the letter does
|
|
196
|
+
nothing rather than quietly doing nothing. `api.ts` refuses on its own too,
|
|
197
|
+
before a request is built, because unreachable has to be a property of the
|
|
198
|
+
module and not of one wiring. The Tools menu drops the separators that had
|
|
199
|
+
been holding the removed items' places rather than opening on a rule with
|
|
200
|
+
nothing above it.
|
|
201
|
+
|
|
202
|
+
The keys describe the **machine, not the science**: `lensing: false` does not
|
|
203
|
+
mean the field has no lens model. Its kappa and mu tiles are ordinary picture
|
|
204
|
+
tiles and keep being drawn, the layer census still counts the model, and the
|
|
205
|
+
About window says the same words on every site. What goes is every way to ask
|
|
206
|
+
a question this deployment cannot answer.
|
|
207
|
+
|
|
208
|
+
Both deployment stories — a collaboration server, and Cloudflare with the
|
|
209
|
+
tiles in R2 and `/api/*` on one hostname through a Tunnel — are now in
|
|
210
|
+
[docs/deploy.md](docs/deploy.md), which has been run end to end.
|
|
211
|
+
|
|
212
|
+
- **`unisky build` builds several layers at once.** Layers are independent, so
|
|
213
|
+
images, catalogs, contours, regions and lens models run in parallel
|
|
214
|
+
processes, and whatever the layers cannot use goes to threads *inside* a
|
|
215
|
+
layer, parallelizing the reprojection bands and the tile encoding. `-j N` is
|
|
216
|
+
a total core budget (`-j 1` strictly serial, `-j 0` every core); the default
|
|
217
|
+
is the smaller of 75% of the cores and as many concurrent layers as fit in
|
|
218
|
+
half of RAM, so it stays sane on a 64-core box and on a laptop alike.
|
|
219
|
+
**The output does not depend on `-j`** — every choice writes byte-identical
|
|
220
|
+
tiles and the same manifest, whose layers stay in config order however the
|
|
221
|
+
build finishes, and the test suite builds the committed real-data fixture
|
|
222
|
+
serially and in parallel and compares every file's hash.
|
|
223
|
+
|
|
224
|
+
- **A build shows what it is doing while it does it**: one in-place bar, plus
|
|
225
|
+
one line per layer *in flight* with its id, its stage, its progress within
|
|
226
|
+
that stage and how long it has been running; finished layers scroll off as
|
|
227
|
+
ordinary log lines, so the scrollback is still a build log. Progress is
|
|
228
|
+
**work-weighted**, not layer-counted — a full-resolution JWST layer writes
|
|
229
|
+
~1000 tiles where a native-scale-capped IRAC layer writes ~80, so each
|
|
230
|
+
layer's cost is estimated up front from the decimated grid it will actually
|
|
231
|
+
tile — and the ETA is **withheld** until a tenth of that weighted work has
|
|
232
|
+
been measured, because dividing by a tiny fraction multiplies its error by
|
|
233
|
+
`(1-f)/f`. It degrades to one plain line per layer whenever stdout is not a
|
|
234
|
+
terminal or `TERM=dumb`; `--no-progress` forces that on a terminal too, and
|
|
235
|
+
`NO_COLOR` is honoured separately (the live display stays, the colour goes).
|
|
236
|
+
A Ctrl-C or a failing layer erases the frame and restores the cursor without
|
|
237
|
+
swallowing the error, and a build with the display on writes byte-identical
|
|
238
|
+
files to one with it off.
|
|
239
|
+
|
|
240
|
+
- **Composite recipes in YAML gained the two things the viewer's RGB panel
|
|
241
|
+
already had.** A channel may be a **weighted sum** of image layers, and the
|
|
242
|
+
three channels may be combined by **Lupton**'s rule instead of one at a
|
|
243
|
+
time:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
channels: {r: f322w2, g: 0.5*f322w2 + 0.5*f150w2, b: f150w2}
|
|
247
|
+
lupton: {stretch: 5, Q: 8}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The sum is the two-filter trick — with only two wide bands there is no
|
|
251
|
+
middle filter for green, so green is synthesized — and weights are
|
|
252
|
+
relative, divided by their sum before mixing, so `1*a + 1*b + 1*c` is the
|
|
253
|
+
average of the three rather than three times the brightness. Lupton scales
|
|
254
|
+
all three channels by one common asinh factor derived from their mean, so a
|
|
255
|
+
bright core keeps the colour of its spectrum instead of saturating to white;
|
|
256
|
+
`lupton: {}` takes astropy's own defaults. Both are written exactly as the
|
|
257
|
+
RGB panel writes them, so a composite designed in the browser can be pasted
|
|
258
|
+
into a config and built — and `tests/test_composite.py` runs the viewer's
|
|
259
|
+
**own** TypeScript against the builder, whole pipeline, holding the two to
|
|
260
|
+
one display level. `examples/rxcj2211.yaml` carries a weighted composite for
|
|
261
|
+
the NIRCam wide pair. See [docs/composites.md](docs/composites.md).
|
|
262
|
+
|
|
263
|
+
- **A stale viewer bundle can no longer be committed unnoticed.**
|
|
264
|
+
`src/unisky/static/` is a build product that lives in git — it is what
|
|
265
|
+
`unisky build` copies into every site and what the wheel ships — so a
|
|
266
|
+
frontend change that was not rebuilt reached every user as the old viewer.
|
|
267
|
+
That had happened three times, and each time a human noticed the wrong thing
|
|
268
|
+
on screen. `just build-fe` now writes `bundle-source.json` beside the
|
|
269
|
+
bundle, recording the SHA-256 of every file vite read and every file it
|
|
270
|
+
wrote; `just check-fe-fresh` — and the ordinary `pytest` run — recompute
|
|
271
|
+
both offline in well under a second and fail naming the files that moved and
|
|
272
|
+
the one command that fixes it. `vite build` empties the directory on its way
|
|
273
|
+
through, so a build that skips the stamping step leaves no stamp at all, and
|
|
274
|
+
no stamp fails loudly rather than passing quietly. See
|
|
275
|
+
[docs/frontend-bundle.md](docs/frontend-bundle.md).
|
|
276
|
+
|
|
277
|
+
- Gravitational **time delays**: a lens model may now carry the lensing
|
|
278
|
+
potential (`maps.psi`, arcsec², glafic's `_pot` dump). With it,
|
|
279
|
+
`/api/lens/findimg` reports `delay_days` for every predicted counter-image
|
|
280
|
+
— the arrival time relative to the first image of that source — and the
|
|
281
|
+
lensing panel labels each image with it. `LensModel` grows
|
|
282
|
+
`fermat_potential()`, `time_delay_distance()` and `delay_days()`;
|
|
283
|
+
`/api/lens/models` and the manifest report `has_potential`.
|
|
284
|
+
- The RGB window can now **build a weighted channel**: "+ band" adds a term
|
|
285
|
+
to a channel, each term gets a weight, and the channel becomes the weighted
|
|
286
|
+
sum the compositor already supported (previously reachable only by editing
|
|
287
|
+
the permalink by hand).
|
|
288
|
+
|
|
289
|
+
### Fixed
|
|
290
|
+
|
|
291
|
+
- **`docs/deploy.md` opened by naming two answers to a question that now has
|
|
292
|
+
three.** "The cutout API needs the original FITS files, so either build on
|
|
293
|
+
the server or mount the dataset at the same absolute path" was the whole
|
|
294
|
+
section, written before there was a third answer — publish the site without
|
|
295
|
+
cutouts and let the deployment say so. The section now names all three, and
|
|
296
|
+
the document goes on to the deployment the third one exists for. Nothing in
|
|
297
|
+
`docs/` or the README had mentioned `--no-cutout`, `--static`,
|
|
298
|
+
`serve.cutout`, `serve.lensing` or `unisky capabilities` at all.
|
|
299
|
+
- **The README's figure for what an `mv` of the cache would strand was one
|
|
300
|
+
count too loose.** It said "68 MB in 68 files"; on the machine it describes
|
|
301
|
+
the number is 66 files, and the line now carries the one-line `diff` that
|
|
302
|
+
produces it, so the next person measures rather than trusts it.
|
|
303
|
+
- **The README told you to destroy the cache it told you to keep.** Its
|
|
304
|
+
upgrade step was `mv ~/.cache/anymap ~/.cache/unisky`, which is right only
|
|
305
|
+
while the destination does not exist — and it exists from the first
|
|
306
|
+
`unisky build` onwards, after which `mv` nests the old cache at
|
|
307
|
+
`~/.cache/unisky/anymap/`, where `cache_root()` never looks. The command
|
|
308
|
+
stranded exactly the downloads it existed to preserve, and exited 0. It is
|
|
309
|
+
now an `rsync -a --update` merge that keeps whichever copy of a file is
|
|
310
|
+
newer, works whether or not the destination exists, prints what it moved,
|
|
311
|
+
and is followed by a `diff` that names anything left behind. Corrected in
|
|
312
|
+
the README, in this entry above, and in `docs/remote-data.md`.
|
|
313
|
+
- **Three more things the README claimed that a real machine refuted.**
|
|
314
|
+
`python3 -m venv .venv` picks up Python 3.9 on stock macOS, below the
|
|
315
|
+
`>=3.10` two lines above it, and the failure that follows is pip 21.2.4
|
|
316
|
+
blaming `setup.py` — so the interpreter is now named and checked, and pip
|
|
317
|
+
upgraded, before anything is installed. `UNISKY_RXCJ2211=… just test` was
|
|
318
|
+
described as running the whole real-data set, which it cannot promise: it
|
|
319
|
+
says where the dataset is, and each test decides by opening a file. And a
|
|
320
|
+
grid-clash refusal was quoted verbatim from a version that no longer
|
|
321
|
+
existed; the quote is replaced by the `--dry-run` that prints the current
|
|
322
|
+
one. Running the rest of the file end to end from a fresh clone also turned
|
|
323
|
+
up `just e2e` needing `npx playwright install chromium`, which `just setup`
|
|
324
|
+
does not do, and `pip uninstall anymap` needing `-y` to survive a
|
|
325
|
+
non-interactive shell.
|
|
326
|
+
|
|
327
|
+
- **The viewer's refusal of a pre-rename site expired after 60 seconds**,
|
|
328
|
+
leaving a blank black page with nothing in the console either. The three
|
|
329
|
+
toasts raised when `boot()` gives up — `manifest.json` unreachable, refused
|
|
330
|
+
for a missing `unisky_version`, or a throw on the way up — are not notices
|
|
331
|
+
on top of a working map; they are the whole page, so they now wait to be
|
|
332
|
+
dismissed instead of timing out. Ordinary toasts still fade after 5 s.
|
|
333
|
+
- Restoring the lensing panel from its dock button drew no critical curve —
|
|
334
|
+
only the menu-bar route refreshed it. `WindowSpec` grows an `onShow` hook
|
|
335
|
+
that fires for every route back on screen.
|
|
336
|
+
- **Coverage footprints drew as near-opaque slabs**, hiding the image they
|
|
337
|
+
are meant to be read against. The opacity slider's generic vector path
|
|
338
|
+
stamped `fillOpacity: opacity * 0.8` on every child of every overlay, so a
|
|
339
|
+
footprint's deliberately faint 0.07 fill (and a region's 0.6) was replaced
|
|
340
|
+
rather than scaled the moment the layer was enabled. It now scales each
|
|
341
|
+
overlay's OWN fill, remembered once so repeated slider ticks cannot
|
|
342
|
+
compound. Catalogs were already immune — they override `setOpacity` to
|
|
343
|
+
defend against exactly this — which is why it surfaced only with the new
|
|
344
|
+
`footprint` layers.
|
|
345
|
+
- **Popups rounded coordinates to 4 significant digits**, so a source at
|
|
346
|
+
RA 332.952929 was shown as `333` — 169 arcsec out — and Dec −3.830160 as
|
|
347
|
+
`-3.83`. Coordinate columns are now printed to a fixed 6 decimals (3.6 mas)
|
|
348
|
+
with trailing zeros dropped; every other column keeps the 4-significant-
|
|
349
|
+
digit rule. Affects built-in and uploaded catalogues alike.
|
|
350
|
+
- **A 64-bit catalogue id read `48420000000000000` for every source.** Two
|
|
351
|
+
independent bugs produced that one number. The popup printed an integer
|
|
352
|
+
verbatim only below 1e7 and sent everything above through a 4-significant-
|
|
353
|
+
digit rounding, so a DESI `ls_id` near 4.84e16 kept four digits and gained
|
|
354
|
+
sixteen zeros — and its neighbours, sharing those four digits, became the
|
|
355
|
+
same id. An identifier is a name that happens to be spelled in digits, so an
|
|
356
|
+
integer is now printed in full at any magnitude. Fixing the formatter alone
|
|
357
|
+
was not enough: 4.8e16 is five times past 2^53, so an id written as a JSON
|
|
358
|
+
*number* is already rounded by the time `JSON.parse` returns. The builder now
|
|
359
|
+
writes an integer outside JavaScript's safe range as a JSON **string** and
|
|
360
|
+
the viewer renders it as one; ordinary small integers stay numbers, so a
|
|
361
|
+
short id still sorts and compares numerically. Catalogues dropped onto the
|
|
362
|
+
map in the browser follow the same rule.
|
|
363
|
+
|
|
364
|
+
## [0.1.0] - 2026-08-20
|
|
365
|
+
|
|
366
|
+
First release.
|
|
367
|
+
|
|
368
|
+
### Added
|
|
369
|
+
|
|
370
|
+
- `unisky build`: config-driven builder producing a self-contained static
|
|
371
|
+
site — quantized 16-bit PNG data tiles with client-side stretch, composite
|
|
372
|
+
RGB tiles, catalog/contour/region GeoJSON overlays, and lens-model grids.
|
|
373
|
+
- `unisky serve`: FastAPI server for the built site plus the science API:
|
|
374
|
+
native-grid FITS cutouts (`/api/cutout`) and strong-lensing endpoints
|
|
375
|
+
(`/api/lens/critcurve`, `/api/lens/mu`, `/api/lens/findimg`,
|
|
376
|
+
`/api/lens/models`); optional basic auth and CORS.
|
|
377
|
+
- Leaflet + WebGL frontend with grouped layers, stretch/colormap panel,
|
|
378
|
+
catalog popups, coordinate readout and goto box, cutout tool, lensing
|
|
379
|
+
panel with z_s slider, and URL-hash permalinks.
|
|
380
|
+
- RXCJ2211-0350 production example config (`examples/rxcj2211.yaml`) covering
|
|
381
|
+
JWST NIRCam, HST RELICS, ALMA continuum + contours, Spitzer IRAC, merged
|
|
382
|
+
photometry with EAZY photo-z join, DS9 regions, and the glafic v0.91 lens
|
|
383
|
+
model.
|
|
384
|
+
- Real-data fixture subsampler (`scripts/make_test_field.py`), a committed
|
|
385
|
+
sub-3 MB tiny field, and a six-spec Playwright E2E suite.
|
|
386
|
+
- Deployment guide (systemd + nginx + basic auth) and `just` dev targets.
|
unisky-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Mingyu Li
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|