Calibration Selectors
Calibration selectors are Python classes that implement the logic to select a calibration from the local calibration database given input metadata from a DRP (Data Reduction Pipeline). All calibration selectors inherit from CalibrationSelector.
The local calibration database uses sqlite-utils, a Python library that provides a convenient interface to SQLite databases. The sqlite-utils Table (store.local_db.table) object provides direct access to sqlite-utils query methods.
How Selectors Work
The selection process consists of the following three steps. Calling select() executes this sequence in order.
Get Candidates:
get_candidates()queries the database for calibrations matching basic criteria.Select Best:
select_best()chooses the best calibration using domain-specific logic.Fallback: If no candidate is selected,
select_fallback()is called as a last resort.
Defining a Selector
To create a custom selector, inherit from CalibrationSelector and implement the required method:
get_candidates(input, db, **kwargs): Query the database and return a list of candidate calibration dictionaries that match basic criteria.
db is an instance of LocalCalibrationDB.
See CalibrationSelector for more details on optional methods.
Queries use the sqlite-utils API to interact with the local calibration database. See the sqlite-utils documentation for details on available query methods. Below are common query patterns.
Example Selector
Below is an trimmed-down example selector for selecting dark calibrations for HISPEC, which has two spectrographs (BSPEC and RSPEC), each with their own detector.
The column instrument_era represents the stability periods of the instrument which might correspond to instrument servicing, etc.
from koa_middleware import CalibrationSelector, LocalCalibrationDB
class DarkSelector(CalibrationSelector):
"""
Selector for dark calibrations.
"""
def get_candidates(self, meta, db : LocalCalibrationDB, **kwargs) -> list[dict]:
"""
Retrieve candidate dark calibrations matching the input metadata.
Parameters
----------
meta : dict-like
Input metadata from the observation.
db : LocalCalibrationDB
The koa_middleware local calibration database instance.
"""
# Build SQL query for candidate calibrations
sql = """
cal_type = :cal_type
AND instrument_era = :instrument_era
AND spectrograph = :spectrograph
"""
params = {
'cal_type': 'dark',
'instrument_era': meta['instrument_era'],
'spectrograph': meta['spectrograph'],
}
# Fetch all matching rows from the database
# Order by closest in time to input observation using mjd_start
rows = list(db.table.rows_where(
sql, params,
order_by=f"ABS(mjd_start - {meta['mjd_start']})"
))
return rows
Using a Selector
Once you’ve defined a selector, use it with the CalibrationStore:
from koa_middleware import CalibrationStore
# Initialize the store
with CalibrationStore(instrument_name='my_instrument') as store:
# Create your selector
selector = DarkSelector()
# Define input metadata for your observation
input_metadata = {
'drp_version': '0.0.1',
'spectrograph': 'BSPEC',
'datetime_obs': '2024-09-24T12:00:00.000',
}
# Select and get the calibration in one step
calibration_filepath = store.select_and_get_calibration(
input_metadata,
selector
)
# Or get both the file path and metadata
calibration_filepath, calibration_record = store.select_and_get_calibration(
input_metadata,
selector,
)
print(f"Using calibration: {calibration_record['id']}")
print(f"File location: {calibration_filepath}")
Query Patterns with sqlite-utils
The attribute LocalCalibrationDB.table provides access to sqlite-utils methods, and the LocalCalibrationDB class provides additional convenience methods. Here are common query patterns for get_candidates:
Basic Queries
# Query with WHERE clause
rows = list(db.table.rows_where(
"cal_type = :type AND master_cal = 1",
{"type": "dark"}
))
# Query all rows
all_rows = list(db.table.rows)
# Query with ordering
rows = list(db.table.rows_where(
"cal_type = :type",
{"type": "dark"},
order_by=f"mjd_start DESC"
))
Conditional Queries
# Range queries
rows = list(db.table.rows_where(
"mjd_start >= :min AND mjd_start <= :max",
{"min": 60000.0, "max": 60100.0}
))
# Multiple conditions
rows = list(db.table.rows_where(
"""cal_type = :type
AND instrument_era = :era
AND datetime_obs > :date""",
{"type": "dark", "era": "0.0.1", "date": "2024-01-01T00:00:00.000"}
))
Sorting and More Complex Queries
Additional filtering can always be done after querying using Python built-ins.
# Get most recent calibration
latest = db.table.rows_where(
"cal_type = :type",
{"type": "dark"},
order_by=f"ABS(mjd_start - {meta['mjd_start']})", # Closest in time
limit=1
)
For complete sqlite-utils documentation, see https://sqlite-utils.datasette.io/en/stable/python-api.html