Quickstart
nexsci-rv searches two radial-velocity archives at NExScI: NEID (a
spectrograph on the WIYN 3.5-m at Kitt Peak) and EXPRES (here, its
solar feed). Every search starts by picking a data level — how
far the data has been processed, from raw frames up to science-ready velocities
(see Data levels) — and a query method that matches what you
already know about the data you want (see Choosing a query).
This page walks the patterns most users reach for first. Every example assumes:
from nexsci_rv import NEIDArchive, EXPRESArchive
neid = NEIDArchive()
expres = EXPRESArchive()
All query methods return an astropy.table.Table;
download() returns list[pathlib.Path].
Querying
By date range
tbl = neid.query_datetime("l2", ("2024-11-01", "2024-11-06"))
print(tbl)
date_range is inclusive (start, end); either side can be None for an
open-ended interval. Dates accept anything astropy.time.Time can parse —
ISO strings, datetime.datetime, Time instances themselves.
By sky position
from astropy.coordinates import SkyCoord
from astropy import units as u
tbl = neid.query_position("l2", SkyCoord("12h00m00s", "+00d00m00s"), 0.5 * u.deg)
coord accepts a astropy.coordinates.SkyCoord or an
(ra_deg, dec_deg) tuple; radius accepts an Angle/quantity or a bare float
(degrees).
By object name
tbl = neid.query_object("l2", "HD 189733", radius=30 * u.arcsec)
query_object resolves the name via Simbad/NED and then runs a position
search. For exact-match on a column (no resolution), use query_qobject.
By PI or program
tbl = neid.query_pi("l2", "Smith")
tbl = neid.query_program("l2", "2024A-001")
Combined criteria (one round-trip)
tbl = neid.query_criteria({
"data_level": "l2",
"datetime": ("2024-11-01", "2024-11-06"),
"object": "HD 189733",
"object_radius": 1 * u.arcmin,
})
query_criteria composes constraints into a single ADQL query so the server
only does one search. Accepted keys (besides data_level):
datetime, position, object, qobject, program, pi_name, where.
Raw ADQL escape hatch
tbl = neid.query_adql(
"SELECT TOP 100 l2filename, obsdate FROM neidl2 "
"WHERE obsdate > to_date('2024-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')"
)
query_adql is the unmediated path — caller owns the query string. Most users
should hit query_criteria first.
Column selection and limits
Every query method takes columns= and maxrec=:
tbl = neid.query_datetime(
"l2",
("2024-11-01", "2024-11-06"),
columns=["l2filename", "obsdate", "object", "exptime"],
maxrec=500,
)
Those columns are: l2filename the FITS file’s name (keep it if you plan to
download), obsdate the observation time stamp,
object the free-text target name, and exptime the exposure time
in seconds. For a tour of the columns you’ll meet most — RV values, quality
metrics, download keys — see Understanding your results.
Unknown columns raise UnknownColumn. List the
columns a table exposes with neid.describe_table("neidl2").
Output formats
as_csv = neid.query_datetime("l2", ("2024-11-01", "2024-11-06"), output_format="csv")
Choices are "votable" (default), "csv", and "ipac". They differ only in
how the data is sent over the wire; all three parse into an
astropy.table.Table. VOTable carries each column’s declared type, so it is
the most robust choice and rarely needs changing.
Authentication (NEID only)
NEID public data is queryable without authentication; data still inside its proprietary period requires login.
neid.login() # prompts for username + password
neid.login(username="me", password="…") # explicit
# Or set:
# export NEXSCI_RV_NEID_USER=me
# export NEXSCI_RV_NEID_PASSWORD=…
The session is cached, so you log in once and later runs reuse it.
expres.login() raises NotApplicable
— EXPRES solar data is fully public.
Downloading FITS
tbl = neid.query_datetime("l2", ("2024-11-01", "2024-11-02"), maxrec=10)
paths = neid.download(tbl, outdir="data/", workers=4, resume=True)
workers=4by default (astronomy archives throttle aggressively).resume=Trueskips files already present at full size.tqdmprogress bar; passprogress=Falseto suppress.If some files fail,
DownloadErroris raised after the rest finish; successes are still returned in the path list, and the exception carries.failures: list[tuple[str, Exception]].
download locates each file using the filename/filepath columns of your table,
so keep those in any columns= selection. See
Querying vs downloading, and proprietary data.
Long-running queries (async TAP)
For queries that would time out under the sync TAP/sync endpoint, submit
asynchronously:
job = neid.submit_async(
"SELECT obsdate, exptime, object FROM neidl2 "
"WHERE obsdate BETWEEN "
"to_date('2023-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS') AND "
"to_date('2024-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')"
)
job.wait(timeout=600) # polls every 5s by default
tbl = job.result()
The job has .phase, .error, .status_url, .result_url, and a
refresh() /
abort() pair. See
AsyncJob.
EXPRES solar specifics
tbl = expres.query_datetime(
"solarl2",
("2024-11-01", "2024-11-06"),
columns=["l2filename", "obsdate", "v", "e_v", "dvrms", "extsnr"],
)
data_levelis"solarl1"or"solarl2".The datetime column is
obsdate, the same as NEID.The RV columns are v / e_v (EXPRES) —
vis the radial velocity ande_vits uncertainty (the EXPRES equivalents of NEID’sccfrvmod/ccfnoise).query_position,query_object, andquery_qobjectare not available for EXPRES solar data — those rows have no sky target (the Sun has no fixed celestial coordinate), so a position search has nothing to match. Calling them raisesNotApplicable.query_piworks onsolarl1(columnobserver) but not onsolarl2(no observer column).
See Archives → EXPRES for the full quirk catalog.
Configuration
Per-instance:
neid = NEIDArchive(server="https://neid.ipac.caltech.edu", timeout=120)
To change several settings at once, build a
Conf and hand it to the archive. Conf is
immutable, so you construct one rather than mutating the defaults:
from nexsci_rv import NEIDArchive, Conf
neid = NEIDArchive(conf=Conf(timeout=120, download_workers=8))
Errors at a glance
Catch broadly with NexsciRVError, or
narrowly:
from nexsci_rv import (
AuthExpired, LoginFailed, NotApplicable, NotAuthenticated,
TapServerError, UnknownColumn, UnknownDataLevel, DownloadError,
)
See API → Exceptions for the full hierarchy.
Logging
To see what’s happening under the hood — the queries being sent and the
responses coming back — turn on logging for the nexsci_rv logger:
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("nexsci_rv").setLevel(logging.DEBUG)
DEBUG-level entries include the ADQL query and a summary of the parsed response.
Next steps
Example notebooks under Tutorials