A small web app that wraps SPISEA with a FastAPI backend and a React + Vite + TypeScript frontend so you can explore synthetic clusters, isochrones, color–magnitude diagrams, and unresolved spectra interactively in the browser.
This repository is a thin client around the SPISEA Python package. It does not
modify or vendor SPISEA — spisea must be importable in the Python
environment that runs the backend.
Originally created for Dan Weisz's Stellar Pops class. Code and documentation generated with support from Cursor.
- Python 3.10+ (the backend uses PEP 604 union syntax such as
list[str] | None). - Node.js 18+ and npm for the frontend.
- A working SPISEA install plus its data dependencies. See
SPISEA's setup notes for the full list.
At a minimum you need:
pysynphotand a CDBS data tree (typically pointed to viaPYSYN_CDBS)- SPISEA's evolution / atmosphere model data (
SPISEA_MODELS) - SPISEA's
filt_func/directory (SPISEA_FILT_FUNC, see below)
- The COSMIC evolution model is optional. To use it (and edit its
BSEDictbinary-evolution parameters in the UI) you must also install COSMIC (pip install cosmic-popsynth) in the backend environment. COSMIC evolves binaries directly, so it requires a multiplicity model; the IFMR and unresolved-spectrum options are ignored when it is selected.
The backend imports spisea directly. Pick one of:
# Option 1: install from GitHub (cleanest for deployments)
pip install git+https://github.com/astropy/SPISEA.git
# Option 2: install an editable copy from a local SPISEA checkout
pip install -e /path/to/SPISEAIf you use Option 1, the SPISEA filt_func/ data directory (which lives at
the SPISEA repo root, not inside the spisea/ package) will not be present.
In that case set SPISEA_FILT_FUNC to point at a filt_func/ directory from
a SPISEA checkout, e.g.:
export SPISEA_FILT_FUNC=/path/to/SPISEA/filt_funcIf you use Option 2, the bridge auto-discovers filt_func/ next to the SPISEA
checkout, so SPISEA_FILT_FUNC is optional.
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# plus SPISEA itself, see above
uvicorn app.main:app --reload --port 8000The API listens on http://127.0.0.1:8000 and exposes /api/meta,
/api/cluster, /api/cluster-fits, /api/cluster-fits-batch,
/api/cluster-notebook, and /api/isochrone-cmd.
| Variable | Purpose |
|---|---|
SPISEA_FILT_FUNC |
Path to SPISEA's filt_func/ directory (filter discovery). |
PYSYN_CDBS |
CDBS root for pysynphot (atmosphere & throughput tables). |
SPISEA_MODELS |
SPISEA evolution/atmosphere model directory. |
SPISEA_WEB_ISO_DIR |
Where the backend caches IsochronePhot FITS files. |
SPISEA_WEB_CDBS_FILTER_MAX |
Cap on CDBS throughput files enumerated for filter list. |
SPISEA_WEB_STATIC_DIR |
Directory of the built frontend the backend serves at / (production only; defaults to /app/static). |
cd backend
pytestThe API tests reuse SPISEA's bundled isochrone fixtures via
os.path.dirname(spisea.__file__)/tests/isochrones, so they require an
installed SPISEA whose tests/isochrones/ directory is present (e.g. an
editable install from a SPISEA git checkout).
cd frontend
npm install
npm run devVite serves on http://127.0.0.1:5173 and proxies /api/* to the backend at
http://127.0.0.1:8000 (see frontend/vite.config.ts).
For a production build:
npm run build
npm run previewspisea-web/
backend/ FastAPI app + SPISEA bridge
app/
main.py FastAPI routes
bridge.py SPISEA object construction, isochrone cache, serializers
models.py Pydantic request/response models
web_filter_aliases.py
tests/
requirements.txt
frontend/ React + Vite + TS SPA
src/App.tsx Main UI (CMD, mass function, spectrum panels)
vite.config.ts Dev proxy to the backend
In production the app runs as a single container: the FastAPI backend
serves both the built frontend (at /) and the /api/* routes, so no separate
web server is needed. SPISEA and its data are bind-mounted from CFS rather
than baked into the image, and COSMIC is installed in the image.
Deployment files in this repo:
| File | Purpose |
|---|---|
Dockerfile |
Multi-stage build: Vite build, then a Python runtime that serves static assets + the API. |
docker-entrypoint.sh |
Adds the mounted SPISEA checkout (SPISEA_SRC, default /spisea) to PYTHONPATH and ensures the cache dir exists. |
backend/requirements-spisea.txt |
SPISEA's scientific dependencies + cosmic-popsynth (SPISEA source itself comes from CFS). |
.dockerignore |
Keeps build context small. |
deploy/spin/*.yaml |
Reference Deployment / Service / Ingress manifests (templates with <PLACEHOLDER>s). |
- A NERSC account with your project visible in SPIN, plus completed SpinUp
training and access to
registry.nersc.gov. - A SPISEA checkout and its data trees staged on CFS, for example:
/global/cfs/cdirs/<PROJECT>/spisea-web/
SPISEA/ # full SPISEA git checkout (spisea/ package + filt_func/)
data/cdbs/ # -> PYSYN_CDBS=/data/cdbs
data/models/ # -> SPISEA_MODELS=/data/models
Ensure the directories are traversable (o+x from root to the mount point) so
SPIN can bind-mount them.
From a NERSC login node (or anywhere with the NERSC registry):
TAG="registry.nersc.gov/<PROJECT>/<NAMESPACE>/spisea-web:v1"
podman-hpc build -t "$TAG" . # or: docker build -t "$TAG" .
podman-hpc push "$TAG"Log in to https://rancher2.spin.nersc.gov/, pick the development or production cluster, and create/select a namespace in your project. Then either:
- Apply the manifests in
deploy/spin/after replacing every<PLACEHOLDER>(image, namespace, NERSC UID/GID, CFS paths), or - Recreate the equivalent settings in the Rancher UI (Workloads → Create →
Deployment), using
deploy/spin/deployment.yamlas the checklist.
Key SPIN requirements baked into the manifests:
- Pull secret
registry-nersc(required even for public images). - Container name must stay
container-0(renaming triggers a Rancher UI bug). - Security context: drop
ALLcapabilities, add back onlyNET_BIND_SERVICE; setrunAsUser/runAsGroup/fsGroupto your NERSC UID/GID (required for the CFS mounts). - Storage: read-only CFS bind-mounts for the SPISEA checkout and data, plus
a writable
nfs-client-vastPVC forSPISEA_WEB_ISO_DIR.
Create a ClusterIP service on port 8000 (deploy/spin/service.yaml) and an
nginx Ingress (deploy/spin/ingress.yaml). The generated host is
<ingress>.<namespace>.<environment>.svc.spin.nersc.org (note .org).
The Ingress sets proxy-read/send/connect-timeout: "900" because COSMIC cluster
runs can take up to 900s (see _COSMIC_TIMEOUT_SEC in backend/app/main.py);
without this, long runs return 504 Gateway Timeout. For a public HTTPS URL,
CNAME a custom domain to the generated host and attach a TLS certificate secret
to the Ingress.
Use Execute shell on the pod in Rancher:
python -c "import spisea; print(spisea.__file__)"
echo "$PYSYN_CDBS $SPISEA_MODELS"
curl -s http://127.0.0.1:8000/api/meta | headGPL v3+ (see LICENSE). SPISEA itself is GPL v3+.