NEID

NEID is a high-precision radial velocity (RV) spectrograph on the WIYN 3.5-m telescope at Kitt Peak — precise enough to detect the small wobble a planet induces on its host star. Its archive at NExScI carries the full reduction hierarchy for stellar targets (raw frames through RV-ready products) plus a parallel solar feed that observes the Sun as a star during the day. This page is your map to what NEID offers and how to query it.

from nexsci_rv import NEIDArchive
neid = NEIDArchive()

Which data level do I want?

A data level is the first argument to every query. It picks how far the data has been processed — and therefore which table you search. Frame it as one question: how processed do you need the data to be?

Want…

Use

Table

A final RV time series (the common case)

L2

neidl2

Reduced 1D spectra to re-analyze yourself

L1

neidl1

Raw 2D detector frames to reduce from scratch

L0

neidl0

Instrument / calibration diagnostics

eng (engineering level)

neideng

Each level also has a solar feed twin for the Sun-as-a-star data: solarl0, solarl1, solarl2, and solareng (tables neidsolarl0 … neidsolareng). The solar feed is a dense, high-cadence RV series used to study solar activity and to validate the instrument’s long-term stability.

You pass the level string ('l2', 'solarl2', …), never the table name; the package translates for you. Start with 'l2' for stellar science and 'solarl2' for the solar feed — that is where the radial velocities live. For the full picture see Data levels.

What L2 gives you: the CCF and the RV

L2 is where most users start, so it is worth knowing what comes back. The L2 pipeline runs a CCF (cross-correlation function): it cross-correlates each spectrum against a template of known stellar lines to produce a single average line profile whose center is the Doppler shift — the radial velocity. You never compute this yourself; the archive stores the finished results.

The columns that hold the answer:

  • ccfrvmod — the model radial velocity (m/s). This is the primary RV number; it is what you plot against time.

  • ccfrvsum — an alternate co-added-CCF RV, a cross-check on ccfrvmod.

  • ccfnoise — the photon-noise uncertainty on the RV (your per-point error bar).

  • ccfjdsum — the precise time stamp (a Julian Date (JD / MJD)) for that RV, often the right x-axis for a time series.

For what these numbers mean physically, see Radial velocities and the CCF; for a tour of every column you are likely to use, see Understanding your results.

Querying NEID

Pick a query method by what you already know about the data you want. NEID supports all of them; for the full decision guide see Choosing a query.

# A time window — the safest first query, always works
tbl = neid.query_datetime("l2", ("2024-11-01", "2024-11-06"))

# A sky region, when you have coordinates
from astropy.coordinates import SkyCoord
from astropy import units as u
tbl = neid.query_position("l2", SkyCoord("12h00m00s", "+00d00m00s"), 0.5 * u.deg)

# A star by name (resolved via Simbad / NED)
tbl = neid.query_object("l2", "HD 189733")

# An exact catalog ID (usually a Gaia DR2 source ID; no name resolution)
tbl = neid.query_qobject("l2", "Gaia DR2 4889286574103556352")

# An observing program or PI
tbl = neid.query_program("l2", "2024A-001")
tbl = neid.query_pi("l2", "Smith")

# Several constraints at once, in a single query — more precise results
tbl = neid.query_criteria({
    "data_level": "l2",
    "datetime": ("2024-11-01", "2024-11-06"),
    "object": "HD 189733",
    "object_radius": 1 * u.arcmin,
    "where": "exptime > 60",
})

The q* search columns versus the per-observation columns

NEID rows carry two flavors of identity and coordinate columns, and knowing the difference tells you what a search actually matches:

  • object describes each observation as recorded — the free-text target name the observer typed. It is whatever someone wrote, so it is unreliable for exact matching.

  • qobject / qrad / qdecd are the archive’s indexed search fields: a normalized catalog ID (typically Gaia DR2) and clean RA/Dec in decimal degrees that the archive assigned. qra / qdec hold the same position as sexagesimal strings. Positional and object searches run against these.

So query_position and query_object cone-search on qrad/qdecd (a name is resolved to coordinates first), and query_qobject does an exact match on qobject. If you want every observation of one target with no name-resolution ambiguity, and you know its Gaia ID, query_qobject is the precise choice.

Understanding your results

Every query returns an astropy Table. To see the full column list a table offers — with units and descriptions — call neid.describe_table("neidl2"). It reports the archive’s declared columns; a given query may not return every column listed.

For the common levels, the columns you will actually reach for:

l2 (RV products): l2filename, l2filepath, obsdate, object, exptime, seeing, airmass, ccfrvmod, ccfrvsum, ccfnoise, ccfjdsum, program, piname.

l1 (extracted spectra): l1filename, l1filepath, obsdate, object, exptime, seeing, airmass, swversion.

l0 (raw frames): l0filename, l0filepath, obsdate, object, exptime, obsmode, obstype.

See Understanding your results for a grouped, plain-language tour of what each of these means.

Downloading

The query returns metadata; the spectra themselves are FITS files you fetch with download. It returns a list of pathlib.Path objects.

tbl = neid.query_datetime("l2", ("2024-11-01", "2024-11-02"), maxrec=10)
paths = neid.download(tbl, outdir="data/", workers=4)

download reads the l<n>filename / l<n>filepath columns from the table, so keep them if you trim the result with columns=. If the table has no obvious filename column (for example, you ran a custom query_adql), pass data_level= explicitly so download can build the right URL.

See Downloading FITS files and plotting a spectrum for a full query-download-plot walkthrough.

Authentication (only for proprietary data)

Public NEID data is queryable and downloadable with no login. You only need to authenticate to reach observations still inside their proprietary period — the protected window (often about a year) during which only the observing team can access their files.

neid.login()                                 # interactive getpass prompt
neid.login(username="myuser", password=...)  # explicit (avoid in checked-in code)

Credentials are resolved in this order:

  1. Explicit username / password args.

  2. Environment: NEXSCI_RV_NEID_USER, NEXSCI_RV_NEID_PASSWORD.

  3. A getpass prompt on stderr.

Set NEXSCI_RV_NO_PROMPT=1 to disable the interactive prompt (useful in CI). A successful login stores a session cookie that later runs reuse, so you log in once per machine, not once per script.

If your session expires

A query or download of proprietary data may fail because your session has expired. The client never silently re-uses your password — just call neid.login() again. (Under the hood this surfaces as AuthExpired.)

Choosing a server

NEIDArchive() with no arguments points at the production server (neid.ipac.caltech.edu) — the real, complete data, and the right default. A test instance exists but carries incomplete data, so leave the default unless you have a specific reason.

neid = NEIDArchive()                          # production (default)
neid = NEIDArchive(server="https://…/")       # override the server (rarely needed)

Use production for EXPRES tables

The test server does not carry EXPRES tables. If you touch EXPRES data — and for most NEID work too — production (neid.ipac.caltech.edu) is the right default, which is what NEIDArchive() resolves to with no args. See production vs test server.

A couple of things worth knowing

You will rarely hit these, because the typed query_* methods handle them for you. They matter only if you drop down to query_adql and write ADQL by hand:

  • Dates: pass plain strings ("2024-11-01") or astropy.time.Time objects to the query methods and the client formats them correctly for the server. In raw ADQL you must use the server’s Oracle to_date('YYYY-MM-DD HH24:MI:SS', ...) form; a bare string comparison against a date column is rejected.

  • Table-name spelling: the typed methods always pick the correct table name. In raw ADQL, use the exact spelling (neidl2, expressolarl2, …); a misspelling returns an ORA-00942: table or view does not exist error.