"""Shared construction of catalog star records from scene star parameters.
Both simulator sides build :class:`~spindoctor.support.types.MutableStar`
records from a scene's per-star mappings: the image-side renderer
(``spindoctor.sim.forward.star``) to know what to draw and to report in its
output metadata, and the navigator-side star model
(``spindoctor.nav_model.stars.nav_model_stars_simulated``) to build its
catalog from the filtered idealized view (``obs.nav_params``). Sharing one
builder keeps the two sides' defaults (position, magnitude, spectral class,
PSF window) identical, so a scene that omits a field cannot silently give
the renderer and the navigator different catalogs.
Only idealized star keys are read here. The record's ``dn`` field is the
catalog-derived relative flux ``2.512 ** -(vmag - 4)`` -- a pure function of
the catalog magnitude, matching the real catalog reduction
(``spindoctor.nav_model.stars.catalog``) -- not a rendered pixel value.
"""
from typing import Any, cast
from starcat import Star
from spindoctor.support.types import MutableStar
__all__ = ['DEFAULT_PSF_SIZE', 'star_record_from_params']
# The PSF fitting-window size a star entry gets when the scene omits one,
# in detector pixels. Renderer-side scaling to an oversampled grid must
# materialize this default so the downsample's rescale returns it intact.
DEFAULT_PSF_SIZE: tuple[int, int] = (11, 11)
[docs]
def star_record_from_params(
star_params: dict[str, Any],
*,
index: int,
default_v: float,
default_u: float,
) -> MutableStar:
"""Build one catalog star record from a scene star mapping.
Parameters:
star_params: One scene ``stars`` entry (idealized keys only are read).
index: Zero-based position in the scene's star list; drives the
record's unique number and default name.
default_v: V position used when the entry has no ``v`` (frame centre).
default_u: U position used when the entry has no ``u`` (frame centre).
Returns:
A fully populated star record at the unshifted catalog position.
"""
star = cast(MutableStar, Star())
star.unique_number = index + 1
star.catalog_name = str(star_params.get('catalog_name', 'SIM'))
star.pretty_name = str(star_params.get('name', f'SIM-{index + 1}'))
star.name = star.pretty_name
star.v = float(star_params.get('v', default_v))
star.u = float(star_params.get('u', default_u))
star.move_v = float(star_params.get('move_v', 0.0))
star.move_u = float(star_params.get('move_u', 0.0))
star.vmag = float(star_params.get('vmag', 8.0))
star.spectral_class = str(star_params.get('spectral_class', 'G2'))
star.temperature = Star.temperature_from_sclass(star.spectral_class)
star.temperature_faked = star.temperature is None
if star.temperature is None:
star.temperature = 5780.0
star.johnson_mag_v = star.vmag
bmv = Star.bmv_from_sclass(star.spectral_class or 'G2') or 0.63
star.johnson_mag_b = star.johnson_mag_v + bmv
star.johnson_mag_faked = False
star.ra_pm = 0.0
star.dec_pm = 0.0
star.conflicts = ''
star.psf_size = tuple(star_params.get('psf_size', DEFAULT_PSF_SIZE))
# Catalog-relative flux in the navigator's DN convention (see
# nav_model/stars/catalog.py); derived from vmag, never from the render.
star.dn = 2.512 ** -(star.vmag - 4.0)
return star