Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MickaelRigault/ztfquery/llms.txt

Use this file to discover all available pages before exploring further.

The ztfquery.fields module is the central toolkit for working with ZTF’s fixed sky tessellation. ZTF tiles the observable sky into a predefined grid of fields, each observed by a mosaic camera of 16 CCDs, with every CCD subdivided into 4 quadrants (identified by a readout-channel ID, rcid). The module exposes a global pandas DataFrame (FIELD_DATAFRAME) and a flat array of IDs (FIELDSNAMES) loaded from the bundled ztf_fields.txt catalogue, along with a comprehensive set of functions for querying spatial containment, computing polygon geometry, checking reference image availability, and producing publication-quality sky maps.

Global Data Objects

The module exposes two module-level objects that are available immediately after import:
from ztfquery import fields

# pandas DataFrame indexed by field ID (int)
# Columns: RA, Dec, Ebv, GalLong, GalLat, EclLong, EclLat, Entry
print(fields.FIELD_DATAFRAME.head())

# numpy array of all field IDs (main + secondary grids)
print(fields.FIELDSNAMES[:10])
ObjectTypeDescription
FIELD_DATAFRAMEpandas.DataFrameAll field properties, indexed by field ID. Columns include RA, Dec, Ebv, GalLong, GalLat, EclLong, EclLat.
FIELDSNAMESnumpy.ndarrayFlat array of all field IDs (integers).

Grid Structure

ZTF uses two overlapping grids:
  • Main grid — field IDs below 880. This is the primary survey footprint.
  • Secondary (auxiliary) grid — field IDs above 999.
Use get_grid_field() to retrieve the IDs belonging to each grid:
from ztfquery import fields

main_ids      = fields.get_grid_field("main")       # IDs < 880
secondary_ids = fields.get_grid_field("secondary")  # IDs > 999
all_ids       = fields.get_grid_field("all")

Querying Field IDs

get_fieldid

Filter the full field list by sky position, coordinate ranges, or dust extinction.
fields.get_fieldid(grid=None, decrange=None, rarange=None)
grid
str | None
Restrict to a specific grid. Accepts "main", "secondary", "all", or None (both grids).
decrange
list[float | None] | None
Declination range in degrees. Three formats are supported:
  • None — no restriction.
  • [min, max] — inclusive range; either bound can be None for open-ended.
  • [[min1, max1], [min2, max2], ...] — union of multiple ranges.
rarange
list[float | None] | None
Right-ascension range in degrees. Same format as decrange.
gallrange
list[float | None] | None
Galactic longitude range in degrees.
galbrange
list[float | None] | None
Galactic latitude range in degrees.
ebvrange
list[float | None] | None
Milky Way E(B-V) dust extinction range.
from ztfquery import fields

# All main-grid fields with Dec between -10 and +30 deg
fids = fields.get_fieldid(grid="main", decrange=[-10, 30])

# Fields in a specific RA/Dec box
box_fids = fields.get_fieldid(rarange=[30, 60], decrange=[-5, 5])

# Low-extinction fields above Dec = -20
clean_fids = fields.get_fieldid(grid="main", ebvrange=[None, 0.1], decrange=[-20, None])

Target-to-Field Mapping

get_fields_containing_target

Return every field (and optionally every CCD) that contains a given sky position.
fields.get_fields_containing_target(ra, dec, inclccd=False, buffer=None)
ra
float
required
Right ascension of the target in degrees.
dec
float
required
Declination of the target in degrees.
inclccd
bool
default:"False"
If True, the returned index entries have the form "fieldid_ccdid" instead of plain field IDs.
buffer
float | None
Expand each field polygon by this many degrees before testing containment. Useful to account for the ~0.3 deg inter-CCD gap.
from ztfquery import fields

ra, dec = 195.0, 27.5   # some target position

# Field IDs containing the position
containing = fields.get_fields_containing_target(ra, dec)
print(containing)        # e.g. Index([440, 441], dtype='int64')

# Include CCD-level resolution
containing_ccd = fields.get_fields_containing_target(ra, dec, inclccd=True)
print(containing_ccd)   # e.g. ['440_7', '441_12']
get_fields_containing_target requires shapely (pip install shapely). Performance is greatly improved with geopandas (pip install geopandas), which enables a vectorised GeoSeries lookup. Without geopandas the function falls back to a pure-Python loop over every field polygon.

get_field_ccd_qid

Resolve a sky position to its exact field, CCD, quadrant ID, and rcid.
fields.get_field_ccd_qid(ra, dec)
Returns a dictionary keyed by fieldid (int), with each value being a dict containing ccd, qid, and rcid.
from ztfquery import fields

result = fields.get_field_ccd_qid(195.0, 27.5)
# {440: {'ccd': 7, 'qid': 3, 'rcid': 26}}

for fieldid, info in result.items():
    print(f"Field {fieldid}: CCD {info['ccd']}, quadrant {info['qid']}, rcid {info['rcid']}")

spatialjoin_radec_to_fields

Perform a bulk spatial join between a table of coordinates and a set of field polygons. Requires geopandas.
fields.spatialjoin_radec_to_fields(radec, fields, how="inner", predicate="intersects")
radec
pandas.DataFrame | numpy.ndarray
required
Sky positions to match. Either a DataFrame with "ra" and "dec" columns, or an (N, 2) array.
fields
dict | GeoSeries | GeoDataFrame
required
Field geometries. Accepts a {fieldid: vertices_array} dict, a GeoSeries indexed by field ID, or a GeoDataFrame with a "fieldid" column.
how
str
default:"\"inner\""
Join type passed to geopandas.GeoDataFrame.sjoin.
predicate
str
default:"\"intersects\""
Spatial predicate passed to sjoin (e.g. "contains", "within").
import numpy as np
import pandas as pd
from ztfquery import fields

# 1000 random sky positions
rng = np.random.default_rng(0)
coords = pd.DataFrame({
    "ra":  rng.uniform(0, 360, 1000),
    "dec": rng.uniform(-30, 90, 1000),
})

field_geom = fields.get_fields_geoserie()  # GeoSeries of all fields
result = fields.spatialjoin_radec_to_fields(coords, field_geom)
print(result[["index_radec", "fieldid"]].head())
spatialjoin_radec_to_fields has a hard dependency on geopandas. Install it with pip install geopandas before calling this function.

Field Geometry

get_field_vertices

Return the boundary vertices of one or more fields as (N, 2) arrays of [RA, Dec] pairs.
fields.get_field_vertices(fieldid=None, inclquad=False, inclccd=False,
                           as_dict=False, as_polygon=False, squeeze=True)
from ztfquery import fields

# Vertices of a single field (full focal plane)
verts = fields.get_field_vertices(440)
# shape: (20, 2) — boundary sampled at 5 points per edge × 4 edges

# CCD-level vertices as a dict: {"440_1": Polygon, ..., "440_16": Polygon}
ccd_verts = fields.get_field_vertices(440, inclccd=True, as_dict=True, as_polygon=True)

get_field_centroid

Return the central RA/Dec (or galactic / ecliptic) coordinate of a field.
fields.get_field_centroid(fieldid, system="radec")
from ztfquery import fields

# Single field
ra_dec = fields.get_field_centroid(440)          # shape (1, 2)

# Multiple fields
centroids = fields.get_field_centroid([440, 441, 500])  # shape (3, 2)

# Galactic coordinates
lb = fields.get_field_centroid(440, system="galactic")

get_corners

Low-level function that computes the four-edge boundary for one or more fields given explicit (ra_field, dec_field) reference coordinates and an optional CCD/quadrant layout.
fields.get_corners(ra_field, dec_field, inclquad=False, inclccd=False,
                    qid=None, ccd=None, steps=5, squeeze=True, inrad=False)

CCD and Quadrant Utilities

ZTF’s focal plane contains 16 CCDs, each split into 4 quadrants. The rcid (readout-channel ID) uniquely identifies a quadrant across the entire focal plane (0–63).

Conversions

from ztfquery import fields

# CCD pixel position → quadrant ID (1-4)
qid = fields.ccdpos_to_qid(ccdx=1500, ccdy=2000)   # qid = 2

# CCD ID + quadrant ID → rcid (0-63)
rcid = fields.ccdid_qid_to_rcid(ccdid=3, qid=2)    # rcid = 9

# rcid → CCD ID + quadrant ID
ccdid, qid = fields.rcid_to_ccdid_qid(rcid=9)      # (3, 2)
The relationship is rcid = 4 × (ccdid − 1) + qid − 1, so rcid runs from 0 (CCD 1, quad 1) to 63 (CCD 16, quad 4).

Centroids

from ztfquery import fields

# Centroid of a specific rcid within a field
pos = fields.get_rcid_centroid(rcid=9, fieldid=440)
# returns [ra, dec] of that quadrant's centre

# Centroids of all 4 quadrants of a given CCD (fieldid + ccdid)
quads = fields.get_qids_centroid(fieldid=440, ccdid=3)
# returns {'q1': [ra1, dec1], 'q2': ..., 'q3': ..., 'q4': ...}

# Alternatively, pass pre-computed CCD vertices directly (skips the vertex lookup)
ccd_verts = fields.get_field_vertices(fieldid=440, inclccd=True, ccd=3)
quads = fields.get_qids_centroid(ccd_vertices=ccd_verts)

Reference Image Status

ZTF builds reference (template) images by coadding multiple exposures. Not every field has been processed for every filter band.

has_field_reference

Check whether a specific field has reference images in each of the three ZTF bands.
fields.has_field_reference(fieldid, rcid_details=False)
fieldid
int
required
The ZTF field ID to query (e.g. 400).
rcid_details
bool
default:"False"
If True, returns a per-rcid count dictionary instead of simple booleans. Useful for identifying partially processed fields.
from ztfquery import fields

# Check field 400
result = fields.has_field_reference(400)
# {'zg': True, 'zi': False, 'zr': True}

print(result["zg"])   # True — g-band reference exists
print(result["zi"])   # False — i-band reference missing

get_fields_with_band_reference

Retrieve all field IDs that have a reference image in a given filter.
fields.get_fields_with_band_reference(filter_, ccdid=1, qid=1)
filter_
str
required
Filter code: "zg" (g-band), "zr" (r-band), or "zi" (i-band).
from ztfquery import fields

# All fields with an i-band reference image
zi_fields = fields.get_fields_with_band_reference("zi")
print(zi_fields[:10])
# array([441, 442, 516, 517, 518, 519, 520, 522, 523, 524])

g_fields = fields.get_fields_with_band_reference("zg")
r_fields = fields.get_fields_with_band_reference("zr")

show_reference_map

Convenience function that calls get_fields_with_band_reference and immediately renders a sky map.
from ztfquery import fields
import matplotlib.pyplot as plt

fig = fields.show_reference_map("zr")
plt.show()

Sky Visualisation

show_fields

The primary function for rendering a sky map of ZTF fields, optionally coloured by a numeric quantity.
fields.show_fields(fields, vmin=None, vmax=None, ax=None, cmap="viridis",
                   title=None, colorbar=True, show_ztf_fields=True,
                   grid="main", show_mw=True, savefile=None, **kwargs)
fields
list | dict | pandas.Series
required
Either a flat list of field IDs (all drawn with the same colour) or a {fieldid: value} dict / pandas.Series to colour fields by a continuous quantity.
vmin
float | str | None
Colour scale minimum. Pass a string (e.g. "5") to use a percentile of the data.
vmax
float | str | None
Colour scale maximum. Same percentile-string syntax as vmin.
cmap
str
default:"\"viridis\""
Matplotlib colormap name.
show_ztf_fields
bool
default:"True"
Overlay the full ZTF field grid as faint outlines.
show_mw
bool
default:"True"
Draw the Milky Way plane.
savefile
str | None
If provided, save the figure to this path (at 150 dpi).
from ztfquery import fields

# Simple coverage map — fields highlighted in green
fig = fields.show_fields(
    fields.get_fields_with_band_reference("zg"),
    facecolor="C2",
    alpha=0.4,
    title="Fields with g-band reference",
)

show_field_ccds

Display the 16-CCD layout of a single field.
fields.show_field_ccds(fieldid, ax=None, ccd=None,
                        textcolor="k", facecolor="0.9", edgecolor="k")
from ztfquery import fields
import matplotlib.pyplot as plt

fig = fields.show_field_ccds(440)
plt.show()

# Show only CCDs 1-8
fig = fields.show_field_ccds(440, ccd=range(1, 9))

show_gri_fields

Render a three-panel (g / r / i) sky coverage map. Any of the three panels can be omitted by passing None.
fields.show_gri_fields(fieldsg=None, fieldsr=None, fieldsi=None,
                        title=" ", alignment="horizontal",
                        show_ztf_fields=True, colorbar=True,
                        show_mw=True, projection="hammer", **kwargs)
fieldsg
list | dict | None
Field IDs (or field→value dict) to show in the g-band (green) panel.
fieldsr
list | dict | None
Field IDs (or field→value dict) to show in the r-band (red) panel.
fieldsi
list | dict | None
Field IDs (or field→value dict) to show in the i-band (orange) panel.
alignment
str
default:"\"horizontal\""
Layout of the three panels. "horizontal" places them side by side; "classic" uses a two-over-one arrangement.
from ztfquery import fields

g_fields = fields.get_fields_with_band_reference("zg")
r_fields = fields.get_fields_with_band_reference("zr")
i_fields = fields.get_fields_with_band_reference("zi")

fig = fields.show_gri_fields(
    fieldsg=g_fields,
    fieldsr=r_fields,
    fieldsi=i_fields,
    title="ZTF Reference Coverage",
    alignment="horizontal",
)
fig.savefig("ztf_reference_coverage.png", dpi=200)

show_ztf_fieldvalues

Show any column from FIELD_DATAFRAME as a coloured sky map.
fields.show_ztf_fieldvalues(key="Ebv", fieldid="main", mindec=-30,
                             vmin=None, vmax=None, cmap="viridis")
from ztfquery import fields

# Map of Milky Way dust extinction across all main-grid fields
fig = fields.show_ztf_fieldvalues(key="Ebv", fieldid="main", mindec=-30, cmap="hot")

# Galactic latitude map
fig = fields.show_ztf_fieldvalues(key="GalLat", cmap="RdBu_r")

Visualisation Classes

For programmatic control over plots and animations, the module exposes three classes.
FieldPlotter manages a Hammer-projection axes and a combined histogram-colorbar, and is the engine behind show_fields.
from ztfquery.fields import FieldPlotter, get_fields_with_band_reference

fplot = FieldPlotter(inclcax=True, inclhist=True)

# Draw ZTF grid outline
fplot.show_ztf_grid(which="main", edgecolor="0.6", alpha=0.15)

# Draw Milky Way plane
fplot.show_milkyway()

# Overplot specific fields
fplot.show_fields(
    get_fields_with_band_reference("zr"),
    facecolor="C3",
    alpha=0.4,
)

fplot.fig.savefig("custom_map.png", dpi=150)
FieldAnimation extends FieldPlotter for animating sequences of ZTF observations. Pass a list of field IDs observed in order and optional per-frame properties.
from ztfquery.fields import FieldAnimation
import numpy as np

# Simulate a sequence of 50 observed fields
observed = np.random.choice(range(200, 600), size=50)
dates    = [f"2024-01-{i+1:02d}" for i in range(50)]

anim = FieldAnimation(
    fields=observed,
    dates=dates,
    facecolors="C0",
    alphas=0.6,
)
anim.launch(interval=120, repeat=True)
PalomarPlanning computes which fields are observable from Palomar on a given night given airmass and twilight constraints.
from ztfquery.fields import PalomarPlanning, get_grid_field

planner = PalomarPlanning(date="2024-06-15")

# Observable fraction of the night for each field
main_fields = get_grid_field("main")
stime, ffrac = planner.get_fields_observability(
    main_fields,
    airmasslimit=[1, 1.5],
    minobservability=None,
)

# Fields observable for at least 90 min
observable = planner.get_observable_fields(main_fields)
print(f"{len(observable)} fields observable tonight")

# Full observability map
fig = planner.show_fields_observability(main_fields)

Build docs developers (and LLMs) love