Gaia DR3 Library

Gaia DR3, local. This dialog is four things in one: a downloader for the full Gaia DR3 XP spectral library (34 million calibrated stellar spectra), a downloader for the full Gaia DR3 astrometric catalog (1.6 billion stars), a 3D Stellar Neighborhood Explorer that puts every nearby star in its real place in galactic coordinates, and a Spectrum Viewer for inspecting individual XP spectra. The spectral and astrometric data power color calibration and plate solving throughout SASpro; the Neighborhood Explorer and the asterism overlay are their own payoff — a tour of what's actually out there within a few hundred parsecs of the Sun.
The scale of what you're installing. The spectral library tops out at 34,414,104 XP spectra (~134 GB at full depth). The astrometric library tops out at 1,585,128,120 stars across 41 files (~184 GB at full depth). The pre-built neighborhood extract is 14,708,327 stars within 300 parsecs with precomputed galactic Cartesian positions — 2.5 GB and the fastest way to see something interesting. You do not need the full catalog to get value; the Bright tiers alone cover most practical use.
Honest about DR3's limits. The DR3 astrometric builder did not capture parallax_error, so SASpro cannot do the standard parallax_over_error > 5 distance-reliability cut. It guards with (a) parallax > 0, (b) a RUWE < 1.4 cut, and (c) a hard 300 pc radius on the neighborhood extract. RUWE flags bad astrometric fits (unresolved binaries, blends) — it does NOT flag a clean fit whose parallax is simply too small to measure, which is why the 300 pc cap matters. Separately, Gaia saturates below G ≈ 2.2 and XP tops out at G ≈ 15, so the brightest naked-eye stars (Sirius, Vega, Alpha Cen, Procyon…) are missing entirely from the Gaia data and faint M dwarfs beyond 15 are missing from the spectra. Where it matters, this dialog says so in-line rather than hiding it. Gaia DR4 (expected late 2025) will add parallax_error and bp_rp and this whole picture gets honestly deeper.

1. The four tabs at a glance

Spectral and astrometric libraries live in separate folders (both configurable) because they're independent downloads — you might want all the astrometry for plate solving but only the Ultra-Bright + Bright spectral tiers for color calibration.

2. Spectrum Library tab — magnitude tiers

XP spectra are 343-point calibrated flux spectra from 336-1020 nm (2 nm step), extracted from Gaia's xp_sampled_mean_spectrum table. Sized by target magnitude range:

Lookup priority. Any SASpro tool that needs an XP spectrum tries (1) your installed bulk library, (2) the live download cache (gaia_xp_cache.sqlite), (3) the live Gaia archive. The library is first because it's instant; the archive is last because it's slow and needs internet. The practical consequence: install enough of the library that your typical frames hit (1) and never see the latency of (2) or (3).

3. Astrometry tab — magnitude tiers and the neighborhood extract

Astrometric data (RA, Dec, pmra, pmdec, parallax, G-mag, RUWE) across 41 physical files grouped into six user-facing tiers. Also hosts the pre-built Stellar Neighborhood extract that powers the 3D explorer:

Separate library folders. Spectral and astrometric libraries have independent locations (set each via the "Change Location…" button on its tab). A full astrometric install at 184 GB probably wants to live on a different drive than your system drive; the migration dialog has move / copy / switch-pointer options and shows per-file progress.

4. Install / Resume / Browse / Remove

Each tier row has four possible actions depending on state:

Change Location migration. The "Change Location…" button opens a dialog with three options for what to do with files already installed: move them to the new location (recommended — frees space on the old drive), copy them (useful for keeping a backup), or switch pointer only (leave files behind; they become inaccessible to SASpro until you move them manually). The move/copy uses shutil.move/copy2 so cross-device transfers work, and the migration is cancellable mid-flight with no data loss.

5. The Neighborhood Explorer — 3D view of your own neighborhood

The headline feature. Open the tab, and you're looking at every Gaia DR3 star with a usable parallax inside the current radius, placed in galactic Cartesian coordinates with the Sun at the origin, the galactic plane in XY, and +Z toward the north galactic pole. A grid shows the midplane; three colored axes label the galactic directions; drop-lines fall from each star to the midplane so you can tell at a glance whether a bright point is far above the disk or just far from you.

Interaction. Drag to orbit · scroll to zoom · Ctrl+drag to pan · left-click a star to select · right-click a star for an action menu (view XP spectrum, resolve in SIMBAD, open in browser, wedge-slice from here, copy source_id). The nearest-star table on the right lists up to 300 stars by distance; clicking a row selects and highlights.

5.1. Region controls

5.2. Color-by modes

Why true color is slow. The XP library's bp_rp column is NULL and its synth_phot table is empty in DR3 (not populated by the bulk builder), so there's no shortcut: SASpro integrates the actual 343-point spectrum against the color matching functions on demand, which costs a zlib.decompress plus three dot products per star. Fine for thousands, impossible for millions. The 20k cap keeps the mode usable; results are cached by source_id so re-entering the mode or reloading the same radius doesn't re-integrate. Hardcoded bright stars already carry a color from B-V and are served instantly.

5.3. Depth cues

5.4. The wedge slice

Right-click any star and pick Wedge slice from here… to open a popup that lets you cut an angular cone through the field centered on that star's galactic direction. You pick two half-angles — "along plane" (how far the wedge opens in galactic longitude, left/right along the disk) and "north-south" (how far it opens in galactic latitude, above/below the disk) — and the wedge stays active across radius and cut changes until you click Clear Wedge.

This is how you isolate a moving group, a stream, or anything else elongated along a direction without rotating the camera to look edge-on. Combine with a shell (Min dist + Radius) and you can slice a thin wedge-of-a-shell: say, a 20°-wide cone through 100-150 pc. The direction locks on the star's galactic position, so if you later change the radius the wedge still points the same way; only Clear Wedge removes it.

5.5. Selection and SIMBAD

Left-click a star in the view (or a row in the nearest-star table): a cyan highlight marker appears and the info panel shows source_id, distance in parsecs and light-years, G mag, absolute MG, RUWE, RA/Dec, and galactic XYZ. For hardcoded bright stars the panel instead shows the SIMBAD identifier, spectral type, B-V-derived Teff, and flags "weak parallax" when the Hipparcos SNR is below 5.

Resolve in SIMBAD does a cone search (20″ then 60″, to accommodate Gaia-J2016 vs SIMBAD-J2000 proper motion); for hardcoded stars it uses the direct identifier. Open in browser opens the SIMBAD web page (sim-coo for Gaia sources — their id often won't resolve directly in SIMBAD since most faint neighborhood dwarfs are uncatalogued; sim-id for named stars).

6. The Asterism Explorer — constellations in true 3D

Pick a constellation from the Asterism dropdown and the Gaia layer hides itself for a tour of what the familiar shape actually looks like in three dimensions. Nine asterisms ship:

Earth View. Click the Earth View button and the camera snaps to the Sun looking toward the asterism's centroid. The 3D lines collapse into exactly the flat pattern you see from Earth. Then drag to orbit and watch the stars pull apart in depth — the pattern breaks, usually dramatically, and you see the real geometry. This is the whole point of the tool: a working demonstration that constellations are an accident of our viewing angle, not structures.
Distances are honest about where they aren't precise. RA/Dec are rock-solid (sky positions don't move at this precision). Nearby star distances are from Hipparcos/Gaia parallaxes and good to a few percent. Distant OB-star distances (Orion's belt especially, Deneb) are contested in the literature by factors of 2× and are flagged "approx" in the source. The whole point of the demo is the depth spread, not three-sig-fig ranges. Published values for Alnilam alone vary from 400 to 900 pc depending on the method.

While an asterism is active the Gaia layer is paused and radius/cut changes don't affect the view — the asterism owns the camera. Pick "— none —" from the dropdown to clear and return to the Gaia neighborhood.

7. The Spectrum Viewer tab

One star at a time, three search modes:

The matplotlib plot shows BP (336-680 nm, blue fill) and RP (640-1020 nm, red fill) with a shaded flux-error band if present. The info panel below gives source_id, RA/Dec, G mag, and the peak-wavelength / flux range summary.

The Neighborhood Explorer's right-click View XP Spectrum option opens a non-modal popup version of this viewer, so you can keep the 3D view's camera where it is and open several spectra side by side for comparison.

8. Workflow recipes

Minimum useful install (color calibration + the explorer). Spectrum Library → install Ultra-Bright (220 MB) and Bright (1.5 GB). Astrometry → install Stellar Neighborhood (within 300 pc) (2.5 GB). Total: ~4 GB. You now have offline color calibration for every reasonably bright reference star and the full 3D explorer with true-color mode. Good starting point before you commit to the big tiers.
Full color calibration setup. Spectrum Library → install Ultra-Bright + Bright + Medium (~11 GB). Covers calibration stars down to G 12, which is dense enough for most wide-field and narrowband work. Add Faint (50 GB) only if you're doing long-exposure narrowband where the medium tier runs out of reference stars.
Full plate-solving setup. Astrometry → install Bright (G < 17) (16.9 GB) + Stellar Neighborhood (2.5 GB). Covers most wide-field solves and enables the explorer. Add Faint (G 17-18) (15.7 GB) if you do narrow-field solves below 1° FOV.
Explore the ten nearest stars. Neighborhood Explorer → set radius to 5 pc, color by XP Spectrum (true colour), click Reset View. Alpha Centauri, Sirius, Procyon, and friends sit around the Sun; most are hardcoded naked-eye stars (Gaia saturates on them), so they get real colors from B-V and the panel shows SIMBAD identifier + spectral type. Right-click any to open its SIMBAD page.
The Hyades edge-on. Pick Taurus from the asterism dropdown, click Earth View, then orbit. You'll see Aldebaran break away from the Hyades V (it's a 20 pc foreground star, not a cluster member) and the Hyades themselves resolve into a compact clump at 47 pc, with the horns stretching back to 130 pc. This is the clearest demonstration of a constellation that is mostly real geometry with one impostor.
Orion in true 3D. Pick Orion from the dropdown and Earth View. The flat hourglass collapses. Now orbit: Alnilam leaps backward to ~600 pc, Bellatrix stays near at 77 pc, and Betelgeuse hangs around 168 pc. The belt is not a line, the asterism is not a plane. Press Reset View to see the Gaia neighborhood again.
Slice a shell: what's actually at 200-220 pc? Set radius 220, check Min dist and set 200, color by Distance. You see only stars in a 20-parsec-thick shell. Combined with the Scorpius-Centaurus OB association region (galactic longitude ~290-350°), this is how you expose a specific moving group without the near-field haze. The wedge slice (next recipe) narrows it further.
The wedge trick. Click a star you suspect is in a stream or association, right-click, Wedge slice from here…, pick a narrow along-plane half-angle (10-15°) and a tight north-south half-angle (5-10°). The field reduces to a cone of galactic directions centered on that star. Combine with a shell cut (above) and you have a parsec-thick, degree-wide slice through the local field — enough to see whether its neighbors share its distance and velocity direction.

9. Credits and citation

Written by Franklin Marek as part of SetiAstro Suite. Data from Gaia DR3 (Gaia Collaboration et al., 2022, A&A 649, A1; gea.esac.esa.int/archive) — XP spectra from xp_sampled_mean_spectrum, astrometry from gaia_source, calibrated by DPAC. CIE 1931 color matching functions via the Wyman, Sloan & Shirley (2013, JCGT) analytical multi-lobe Gaussian fits. Hardcoded naked-eye star table draws distances from Hipparcos and Gaia parallaxes (via SIMBAD) and derives sRGB from B-V photometry through the same CIE integrator. ICRS → galactic frame transforms via astropy.coordinates. 3D rendering via pyqtgraph.opengl and PyOpenGL. If you publish science using data served through this dialog, cite Gaia DR3 directly and acknowledge DPAC's calibration pipeline.

Technical details

10.1. Data layer — bulk + cache + archive

Every XP spectrum lookup goes through a three-tier priority chain:

  1. Local bulk library (GaiaBulkLibrary) — one sqlite3 connection per installed tier file, opened with PRAGMA query_only = ON. Primary-key lookup on source_id.
  2. Live download cache (GaiaSpectraDB at gaia_xp_cache.sqlite) — populated by the on-demand downloader as fallback hits accumulate.
  3. Live Gaia archive — only hit when neither local source has the id.

Spectra are stored as zlib-compressed 343-element float32 arrays:

\lambda \in [336, 1020]\ \mathrm{nm},\quad \Delta\lambda = 2\ \mathrm{nm}

plus an optional per-point flux error array. The decompressed flux maps onto the shared WL_GRID at load time.

10.2. Dec-zone + RA composite index (why no R-tree)

SQLite's R-Tree extension isn't compiled into every platform's shipped SQLite — especially in frozen builds — so SASpro avoids it. Instead the astrometric builder assigns each star a declination zone:

z = \lfloor (\delta + 90^\circ) / h_z \rfloor

where hz is the zone height in degrees (stored in the file's metadata, typically 1°), and builds a composite index on (dec_zone, ra). Bounding-box queries then scan only the relevant zone-slabs and the relevant RA range inside each. The exact dec filter is applied in Python at the end to tighten the coarse zone bounds. RA wraparound at 0/360° is handled by splitting the query into two ranges when ra_lo > ra_hi.

10.3. Row-count hardcoding

Every GroupDef carries an exact n_items known from the build (54,735 for Ultra-Bright; 656,820,963 for Maximum Depth). The UI reads this directly instead of running COUNT(*) on multi-GB WITHOUT ROWID tables, which is minutes of pointless disk churn to rediscover a known number. The library objects still expose get_group_status() for external callers — but with a header warning that it's slow on the deep tiers.

10.4. Sphere vs cube with the dist_pc index

The neighborhood extract has a dist_pc index. The sphere query is a trivial range scan on it:

d_{\mathrm{pc}} \leq r

The cube query is the interesting one. Three separate BETWEEN clauses on x_pc, y_pc, z_pc couldn't use the dist_pc index, forcing a full-table scan of ~14M rows. Trick: a cube of half-side e is contained in the sphere of radius e√3 (the cube's circumradius):

R_{\mathrm{sph}}(e) = e\sqrt{3}

So the cube query is written as all four predicates together:

|x_{\mathrm{pc}}|, |y_{\mathrm{pc}}|, |z_{\mathrm{pc}}| \leq e,\quad d_{\mathrm{pc}} \leq e\sqrt{3}

The dist_pc clause prunes with the index before the per-axis test runs, turning a full-table scan into an index-range scan plus a cheap filter. Shell queries (Min dist checked) add a floor:

r_{\min} \leq d_{\mathrm{pc}} \leq r_{\max}

which also rides the dist_pc index.

10.5. The wedge query

Angular wedges are expressed purely in galactic Cartesian coordinates — no SQL trigonometry (not guaranteed in a frozen SQLite's math extensions). Given a direction (ℓ0, b0) and half-widths (Δℓ, Δb), the latitude band becomes a z-over-dist bound:

\sin(b_{\min}) \leq z_{\mathrm{pc}} / d_{\mathrm{pc}} \leq \sin(b_{\max})

and the longitude band uses a dot product against the direction unit vector, with the "within Δℓ" condition expressed as a sign plus a cosine inequality:

x_{\mathrm{pc}} \cos\ell_0 + y_{\mathrm{pc}} \sin\ell_0 \geq 0
(x_{\mathrm{pc}} \cos\ell_0 + y_{\mathrm{pc}} \sin\ell_0)^2 \geq (x_{\mathrm{pc}}^2 + y_{\mathrm{pc}}^2) \cos^2(\Delta\ell)

Both ride the dist_pc index because they coexist with the sphere/shell/cube predicates. The longitude test is skipped entirely at Δℓ ≥ 90° ("no longitude limit").

10.6. True color — XP spectrum to sRGB

For each star with an XP spectrum, SASpro integrates the flux against the CIE 1931 color matching functions (via the Wyman-Sloan-Shirley analytical fits — multi-lobe piecewise Gaussians, ~1% accurate, no lookup table):

X = \int_{336}^{1020} f(\lambda)\, \bar{x}(\lambda)\, d\lambda
Y = \int_{336}^{1020} f(\lambda)\, \bar{y}(\lambda)\, d\lambda
Z = \int_{336}^{1020} f(\lambda)\, \bar{z}(\lambda)\, d\lambda

Luminance is normalized out — chromaticity only, because point size already encodes apparent brightness:

(x, y, z) = (X, Y, Z) / Y

Then the standard D65-adapted linear sRGB transform:

\mathbf{c}_{\mathrm{lin}} = \mathbf{M}_{\mathrm{D65}} \cdot (X, Y, Z)^\top

with the sRGB transfer function applied in two cases:

c_{\mathrm{sRGB}} = 12.92\, c_{\mathrm{lin}} \quad \mathrm{if}\ c_{\mathrm{lin}} \leq 0.0031308
c_{\mathrm{sRGB}} = 1.055\, c_{\mathrm{lin}}^{1/2.4} - 0.055 \quad \mathrm{otherwise}

Out-of-gamut negative values (common for very hot blue stars) are clipped to zero. Verified against blackbody spectra: 2500 K → deep orange, 5772 K (Sun) → near-white, 10000 K → blue, with the B/R ratio strictly increasing in temperature. Trapezoidal integration via np.trapezoid with a fallback to np.trapz for pre-NumPy-2.0 installs.

10.7. Distance from parallax

Classic inversion:

d_{\mathrm{pc}} = 1000 / \varpi_{\mathrm{mas}}

Only sources with ϖ > 0 are kept at build time. Absolute magnitude from the distance modulus:

M_G = G - 5 \log_{10}(d_{\mathrm{pc}}) + 5

used for the "Absolute G (luminosity)" color mode. Both values are stored precomputed in the neighborhood extract so neither per-star arithmetic nor a LOG10 extension is needed at query time.

10.8. Color ramps

For the non-true-color modes (absmag, appmag, dist), SASpro computes a per-star scalar v, finds its 5th and 95th percentiles over the loaded sample, and maps:

t = \mathrm{clip}((v - P_5) / (P_{95} - P_5),\; 0,\; 1)

then applies a two-segment ramp: t = 0 → blue-white (luminous), t = 0.5 → warm cream, t = 1 → orange-red (dim). Percentile normalization keeps a few outliers from flattening the entire ramp. Non-finite values (unknown G) sit at t = 0.5.

10.9. Point sizes

Brighter stars render bigger. Normalize G by the 2nd-98th percentile:

t_G = \mathrm{clip}((G - P_2(G)) / (P_{98}(G) - P_2(G)),\; 0,\; 1)

and apply a linear curve where t = 0 (brightest) gets a size multiplier of 2.6 and t = 1 (faintest) gets 0.5:

s = s_{\mathrm{base}} \cdot (2.6 - 2.1\, t_G)

with a minimum of 1.2 pixels so faint stars don't disappear sub-pixel. The slider sets s_base (default 4 px).

10.10. 3D picking via projected screen position

pyqtgraph has no built-in 3D picking, so SASpro does it directly. Transform every displayed point through the current MVP matrix:

\mathbf{c}_{\mathrm{clip}} = \mathbf{M}_{\mathrm{proj}} \cdot \mathbf{M}_{\mathrm{view}} \cdot (x, y, z, 1)^\top

Divide by w to get normalized device coordinates:

(x_{\mathrm{ndc}}, y_{\mathrm{ndc}}) = (c_x / c_w, c_y / c_w)

Map to screen pixels (note the y-axis flip — NDC +Y is up, screen +Y is down):

(x_s, y_s) = \left(\frac{1}{2}(x_{\mathrm{ndc}} + 1) \cdot W,\; \frac{1}{2}(1 - y_{\mathrm{ndc}}) \cdot H\right)

The point with the smallest screen distance to the click wins, with a 12-pixel tolerance and a w > 0 "in front of the camera" filter. Click vs. drag is distinguished by cursor travel: < 4 pixels = click, more = orbit drag. Right-clicks that pass the click test open the context menu; left-clicks emit pointPicked. Cheap enough at these point counts to avoid a dedicated color-picking render pass.

10.11. Projection matrix across pyqtgraph versions

pyqtgraph 0.13.x's projectionMatrix() takes no required arguments; 0.14.0 requires both region and viewport. Calling the 0.13 form on 0.14 raises TypeError; the reverse works but with a region derived from the viewport. SASpro tries the 0.14 signature first, falls back to 0.13 on TypeError, and logs anything else — a prior bare except here silently returned None, which meant every click did nothing on 0.14.

10.12. Asterism 3D placement

Each hardcoded asterism star carries ICRS RA, Dec, and distance in parsecs. Conversion to galactic Cartesian uses astropy's frame transforms, matching the frame the neighborhood extract and the hardcoded naked-eye star table already use. The equivalent closed form (what astropy's transform produces to within precession/nutation corrections):

x = d \cos(b) \cos(\ell)
y = d \cos(b) \sin(\ell)
z = d \sin(b)

where (ℓ, b) are the galactic coordinates derived from the ICRS (α, δ) by astropy's rotation. Results are cached per-asterism so switching between constellations is instant on the second visit.

10.13. Earth View camera math

The Earth View button for asterisms places the camera at the Sun and points it at the asterism's centroid c, so the 3D lines collapse to the familiar sky projection. From centroid position c = (cx, cy, cz) and r = |c|:

\mathrm{azimuth} = \mathrm{atan2}(-c_y, -c_x)
\mathrm{elevation} = \arcsin(-c_z / r)

(the negatives come from the convention that pyqtgraph's azimuth/elevation describe the camera's look-from direction, not look-at). Distance is set to r so the near plane sits at the Sun. Orbit from there makes the depth structure obvious.

10.14. Hardcoded naked-eye stars

Gaia saturates below G ≈ 3, which excludes the brightest ~800 naked-eye stars entirely. SASpro ships a hardcoded table built from Hipparcos / Gaia parallaxes (via SIMBAD cross-match) with per-star SIMBAD identifier, V mag, spectral type, B-V-derived Teff, pre-computed galactic XYZ, pre-computed sRGB (from the same CIE integrator as the XP mode, applied to a B-V-indexed stellar spectrum), and a plx_ok flag (false when the Hipparcos parallax SNR is < 5). Their source_ids are negative as a sentinel — any accidental Gaia lookup fails loudly instead of silently hitting the wrong star. They're folded into every neighborhood load that includes them in its radius; the one plausible overlap (Arneb, Alpha Leporis, G = 2.55, in the XP library) sits at 680 pc, well outside any routine query.

10.15. Lazy GL initialization and the dialog-flicker tradeoff

Constructing a QOpenGLWidget inside an already-visible top-level window makes Qt destroy and recreate that window's native handle. The GL view here is deliberately built in the Neighborhood Explorer tab's showEvent, not in the dialog's constructor. The consequence: the first visit to the Neighborhood Explorer tab shows a brief flicker of this dialog as its native window is rebuilt. The alternative — building the GL view at construction time — would push that flicker to SASpro's main window, which is much worse. A one-off flicker of a modal dialog is the right place to pay the cost. Subsequent visits do nothing (an idempotent _gl_ready flag guards against re-entry if showEvent is pumped during construction).

10.16. Non-blocking downloads and migration

Every download, every file migration, and the neighborhood load itself run on dedicated QThread workers. The main dialog signals through them; a cancel flag lets any in-flight operation stop at the next chunk boundary. The dialog's closeEvent cancels all workers and wait()s for them to settle (2-second timeout) before the dialog actually closes, so a user who hits Close during a 50-GB download gets clean file-system state (partial .tmp cleaned up, no orphan handles).

10.17. DR4 wishlist — what's being worked around

None of these are fixable without a re-fetch when Gaia DR4 lands:

Everything above the DR4 line is a feature of the current data, documented honestly in the UI rather than fixed in post-processing. When DR4 lands, both catalogs get rebuilt and the explorer reaches farther honestly, not just optimistically.