from lenstronomy.SimulationAPI.ObservationConfig.LSST import LSST
from lenstronomy.SimulationAPI.ObservationConfig.Roman import Roman
from lenstronomy.SimulationAPI.ObservationConfig.Euclid import Euclid
import speclite.filters
_OBSERVATORY_REGISTRY = {}
# Default options in SLSim
ROMAN_BAND_LIST = ["F062", "F087", "F106", "F129", "F158", "F184", "F146", "F213"]
LSST_BAND_LIST = ["u", "g", "r", "i", "z", "y"]
EUCLID_BAND_LIST = ["VIS"]
[docs]
def check_speclite_name(band):
"""Checks if the raw band name is a valid speclite filter.
Returns the band name if valid, otherwise returns None. This will
serve as the default speclite_fmt for observatories that use the
same band names as their speclite filters.
"""
try:
# attempt to load the filter from speclite's registry
speclite.filters.load_filter(band)
return band
except ValueError:
# speclite doesn't recognize this exact name
return None
[docs]
def register_observatory(
name: str, observatory_class, bands: list, speclite_fmt=check_speclite_name
):
"""Register a new observatory to integrate it with image simulation tools.
This allows external or user-defined observatories (e.g., "MidEx")
to be automatically recognized by functions like ``kwargs_single_band``.
:param name: The identifier for the observatory (e.g., "MidEx").
:type name: str
:param observatory_class: The class defining the observatory's configuration.
Similar to the ``lenstronomy.SimulationAPI.ObservationConfig`` classes,
this class must fulfill two requirements:
1. Its constructor must accept ``band`` as a keyword argument.
2. It must expose a ``kwargs_single_band()`` method that returns a dictionary of lenstronomy observation parameters.
:type observatory_class: type
:param bands: List of band name strings owned by this observatory. E.g., for LSST this would be ['u', 'g', 'r', 'i', 'z', 'y'].
:type bands: list[str]
:param speclite_fmt: A callable function that takes a ``band`` string and returns the corresponding
speclite filter name (e.g., ``lambda b: f"MidEx-{b}"``). Set to ``None`` if the
observatory does not utilize speclite filters.
:type speclite_fmt: callable, optional
Given below is a simple example of how to define a custom observatory and register it using this function.
A sophisticated example demonstrating full image simulation capabilities can be found at https://github.com/timedilatesme/MidEx-sims/blob/main/v1/lagn_sims.ipynb
Example:
--------
.. code-block:: python
import copy
import lenstronomy.Util.util as util
import slsim.simulation.image_quality_lenstronomy as iql
# Specify bands and their corresponding observation parameters for the custom observatory
custom_band_obs = {
"A": {"exposure_time": 90.0, "sky_brightness": 22.0, "magnitude_zero_point": 26.0},
"B": {"exposure_time": 90.0, "sky_brightness": 21.5, "magnitude_zero_point": 25.8}
}
# Define the observatory class
class CustomObs(object):
\"\"\"Class containing CustomObs instrument and observation configurations.\"\"\"
def __init__(self, band="A", **kwargs):
if band not in custom_band_obs:
raise ValueError(f"Band '{band}' not supported! Choose from {list(custom_band_obs.keys())}.")
self.obs = copy.deepcopy(custom_band_obs[band])
self.camera = {
"read_noise": 5.0,
"pixel_scale": 0.15,
"ccd_gain": 2.0,
}
def kwargs_single_band(self):
return util.merge_dicts(self.camera, self.obs)
# Register the new observatory
iql.register_observatory(
name="CustomObs",
observatory_class=CustomObs,
bands=list(_custom_band_obs.keys()),
speclite_fmt=lambda b: f"CustomObs-{b}"
)
"""
_OBSERVATORY_REGISTRY[name] = {
"class": observatory_class,
"bands": list(bands),
"speclite_fmt": speclite_fmt,
}
# Pre-registered observatories (LSST, Roman, and Euclid)
register_observatory(
name="LSST",
observatory_class=LSST,
bands=LSST_BAND_LIST,
speclite_fmt=lambda band: f"lsst2023-{band}",
)
register_observatory(
name="Roman",
observatory_class=Roman,
bands=ROMAN_BAND_LIST,
speclite_fmt=lambda band: f"Roman-{band}",
)
register_observatory(
name="Euclid",
observatory_class=Euclid,
bands=EUCLID_BAND_LIST,
speclite_fmt=lambda band: f"Euclid-{band}",
)
def _get_observatory_name_for_band(band):
"""Return the observatory name that owns *band*, searching the registry.
:param band: Imaging band name.
:raises ValueError: if no registered observatory claims the band.
"""
for obs_name, info in _OBSERVATORY_REGISTRY.items():
if band in info["bands"]:
return obs_name
raise ValueError(
f"Band '{band}' is not recognised by any registered observatory. "
f"Registered bands: { {o: i['bands'] for o, i in _OBSERVATORY_REGISTRY.items()} }"
)
[docs]
def get_observatory(band):
"""Determine the observatory based on the imaging band.
Queries the registry; works for any registered observatory.
:param band: Imaging band name.
:raises ValueError: if the band does not belong to any observatory.
"""
return _get_observatory_name_for_band(band)
[docs]
def kwargs_single_band(band, observatory=None, **kwargs):
"""Return the lenstronomy single-band keyword dict for a given band.
:param band: Imaging band name (e.g. ``'g'``, ``'F062'``, ``'VIS'``, etc.).
:type band: str
:param observatory: Observatory name. When ``None`` the observatory registry is
queried automatically based on *band*.
:type observatory: str or None
:param kwargs: Additional keyword arguments forwarded to the observatory
class constructor (e.g. ``coadd_years``).
:return: Configuration dict of imaging data for lenstronomy.
:rtype: dict
"""
if observatory is None:
observatory = get_observatory(band)
if observatory not in _OBSERVATORY_REGISTRY:
raise ValueError(
f"Observatory '{observatory}' is not registered. "
f"Registered observatories: {list(_OBSERVATORY_REGISTRY.keys())}"
)
obs_class = _OBSERVATORY_REGISTRY[observatory]["class"]
obs_instance = obs_class(band=band, **kwargs)
return obs_instance.kwargs_single_band()
[docs]
def get_speclite_filtername(band):
"""Get the speclite filter name corresponding to the given band.
:param band: imaging band name
:type band: str
:return: speclite filter name
:rtype: str
:raises ValueError: if the band is not registered or has no speclite
filter.
Default Supported bands:
- LSST: 'u', 'g', 'r', 'i', 'z', 'y'
- Roman: 'F062', 'F087', 'F106', 'F129', 'F158', 'F184', 'F146', 'F213'
- Euclid: 'VIS'
"""
obs_name = get_observatory(band)
fmt = _OBSERVATORY_REGISTRY[obs_name]["speclite_fmt"]
if fmt is None:
raise ValueError(
f"Observatory '{obs_name}' (band '{band}') has no speclite filter registered."
)
return fmt(band)
[docs]
def get_speclite_filternames(bands):
"""Get a list of speclite filter names corresponding to the provided bands.
:param bands: list of imaging band names. E.g., ['u', 'g', 'r', 'F062', 'VIS'].
:type bands: list of str
:return: list of speclite filter names in the same order as input bands
:rtype: list of str
:raises ValueError: if any band is not recognized for any observatory or has no speclite
filter.
Supported bands:
- LSST: 'u', 'g', 'r', 'i', 'z', 'y'
- Roman: 'F062', 'F087', 'F106', 'F129', 'F158', 'F184', 'F146', 'F213'
- Euclid: 'VIS'
"""
return [get_speclite_filtername(band) for band in bands]
[docs]
def get_all_supported_bands():
"""Return every band name currently registered across all observatories.
:return: Flat list of band name strings.
:rtype: list of str
"""
all_bands = []
for info in _OBSERVATORY_REGISTRY.values():
all_bands.extend(info["bands"])
return all_bands