Interactive radio astronomy visualization for Jupyter. Renders radio images on a rotatable celestial sphere with SIN projection, reading directly from zarr stores -- no FITS intermediary.
Existing tools like ipyaladin have significant limitations for radio astronomy workflows:
| Problem with ipyaladin | astrowidget solution |
|---|---|
| Broken sphere overlay for radio images | Correct SIN projection on a rotatable sphere |
| Requires FITS round-trip (zarr -> HDU -> FITS -> display) | Direct binary transfer (zarr -> numpy -> GPU) |
| GPL-3 license (Aladin Lite) | BSD-3, fully owned |
| No native zarr support | open_dataset() reads zarr natively |
| FITS serialization adds latency | Raw float32 transfer, <50 ms per frame |
| No frequency/time slider controls | Built-in time/freq slice navigation |
- Interactive sphere -- Pan, zoom, and rotate the celestial sphere at 60 fps
- Zarr-native -- Load local, remote (S3/HTTPS), or in-memory zarr stores
- WCS coordinate grid -- RA/Dec grid overlay with auto-scaling intervals
- HiPS backgrounds -- Aladin Lite embed for DSS, WISE, Planck survey tiles
- Click-to-inspect -- Click anywhere on the sphere to extract spectrum and light curve
- SkyViewer dashboard -- Panel-based dashboard with controls and linked Bokeh strip charts
- Box zoom -- Drag a rectangle to zoom into a region of interest
- Tested -- 63 Python + 15 JS tests with astropy-validated projection vectors
from astrowidget import SkyWidget, open_dataset
# Load zarr data
ds = open_dataset("path/to/observation.zarr")
# Display on the celestial sphere
widget = SkyWidget()
widget.set_dataset(ds)
widget.background_survey = "DSS" # optional HiPS background
widgetfrom astrowidget import SkyWidget
import numpy as np
from astropy.wcs import WCS
data = np.random.randn(256, 256).astype(np.float32)
wcs = WCS(naxis=2)
wcs.wcs.ctype = ["RA---SIN", "DEC--SIN"]
wcs.wcs.crval = [180.0, 45.0]
wcs.wcs.cdelt = [-0.1, 0.1]
wcs.wcs.crpix = [128.5, 128.5]
widget = SkyWidget()
widget.set_image(data, wcs)
widgetfrom astrowidget import SkyViewer
viewer = SkyViewer.from_zarr("path/to/observation.zarr")
viewer.panel()This opens a Panel dashboard with the sky widget, time/frequency sliders, colormap controls, and linked spectrum + light curve plots that update on click.
pip install astrowidgetOptional extras:
pip install 'astrowidget[dashboard]' # Panel + Bokeh for SkyViewer
pip install 'astrowidget[remote]' # S3/fsspec for remote zarr stores
pip install 'astrowidget[ingest]' # xradio for radio data ingestion
pip install 'astrowidget[hips]' # ipyaladin for SkyWidget.overlay / create_backgroundgit clone https://github.com/uw-ssec/astrowidget.git
cd astrowidget
pixi install
pixi run test # verify everything worksAdd to your pyproject.toml:
[tool.pixi.feature.visualization.pypi-dependencies]
astrowidget = { path = "../astrowidget", editable = true }astrowidget uses pixi for reproducible development environments. All tasks are defined in pyproject.toml:
| Task | Description |
|---|---|
pixi run test |
Run all Python and JS tests |
pixi run test-py |
Run Python tests only |
pixi run test-js |
Run JS tests only |
pixi run lint |
Lint with ruff |
pixi run build |
Build JS bundle with Vite |
pixi run dev |
Watch mode for JS development |
pixi run docs-serve |
Serve docs locally at localhost:8000 |
pixi run docs-build |
Build static documentation site |
pixi run vectors |
Regenerate astropy projection test fixtures |
- JS:
npm run buildbundlesjs/inline_widget.jsintosrc/astrowidget/static/widget.jsvia Vite - Python:
python -m buildcreates sdist + wheel with the bundled JS - CI/CD: GitHub Actions builds and publishes to PyPI on release via trusted publisher
zarr store ──> xarray.Dataset ──> PreloadedCube (LRU cache)
│
float32 slice
│
SkyWidget (anywidget)
│
┌───────┴───────┐
│ Python ←→ JS │
│ (traitlets) │
└───────┬───────┘
│
WebGL2 fragment shader
┌────────────────────┐
│ screen pixel │
│ → gnomonic inv. │
│ → RA/Dec │
│ → SIN proj. │
│ → (l, m) │
│ → texture sample │
│ → stretch │
│ → colormap LUT │
│ → RGBA │
└────────────────────┘
| Component | File | Role |
|---|---|---|
SkyWidget |
src/astrowidget/widget.py |
Anywidget class -- bridges Python data to JS renderer |
open_dataset |
src/astrowidget/io.py |
Unified zarr loader (local, S3, in-memory) |
PreloadedCube |
src/astrowidget/cube.py |
LRU-cached slice loader with strided downsampling |
get_wcs |
src/astrowidget/wcs.py |
WCS extraction from zarr metadata (3 fallback locations) |
SkyViewer |
src/astrowidget/viewer.py |
Panel dashboard with linked spectrum/light curve views |
| WebGL renderer | js/inline_widget.js |
Raw WebGL2 fragment shader with SIN projection |
The core widget for displaying radio images on the celestial sphere.
widget = SkyWidget()
# Load data
widget.set_image(data_2d, wcs) # numpy array + astropy WCS
widget.set_dataset(ds) # xarray Dataset from zarr
# Navigation
widget.goto(SkyCoord(180, 45, unit="deg"), fov=10)
# Display options
widget.colormap = "viridis" # inferno, viridis, plasma, magma, grayscale
widget.stretch = "sqrt" # linear, log, sqrt, asinh
widget.show_grid = True # RA/Dec coordinate grid
widget.auto_scale(5, 99.5) # percentile-based vmin/vmax
# HiPS background
widget.background_survey = "DSS" # DSS, WISE, Planck, 2MASS, ...
widget.background_opacity = 0.5
# Events
widget.clicked_coord # (RA, Dec) of last clickfrom astrowidget import open_dataset
ds = open_dataset("/local/path.zarr")
ds = open_dataset("s3://bucket/obs.zarr", storage_options={"anon": True})from astrowidget import SkyViewer
viewer = SkyViewer.from_zarr("path/to/data.zarr")
viewer.panel() # returns a Panel layout| Layer | Technology |
|---|---|
| Widget framework | anywidget |
| Rendering | Raw WebGL2 (fragment shader) |
| Projection | SIN (slant orthographic) |
| Data format | zarr v2 via xarray + dask |
| Coordinates | astropy WCS |
| Dashboard | Panel + Bokeh |
| Build | Hatchling (Python) + Vite (JS) |
| Environment | pixi (conda-forge) |
| Testing | pytest + vitest |
| CI/CD | GitHub Actions -> PyPI trusted publisher |
Full documentation is available at uw-ssec.github.io/astrowidget:
| Section | Description |
|---|---|
| Getting Started | Installation, first widget, pixi tasks |
| Architecture Overview | System design, data flow, components |
| Data Pipeline | Zarr loading, WCS extraction, caching |
| WebGL Renderer | Fragment shader, SIN projection, colormaps |
| Design Decisions | Why raw WebGL2, inline ESM, uint8 textures |
| OVRO-LWA Integration | Using with ovro-lwa-portal |
| HiPS Backgrounds | Aladin Lite survey configuration |
| API Reference | SkyWidget, open_dataset, SkyViewer |
BSD 3-Clause -- Copyright (c) 2026, UW Scientific Software Engineering Center
If you use astrowidget in your research, please cite:
@software{astrowidget,
title = {astrowidget: Interactive Radio Astronomy Visualization for Jupyter},
author = {{UW Scientific Software Engineering Center}},
year = {2026},
url = {https://github.com/uw-ssec/astrowidget},
license = {BSD-3-Clause}
}