Source code for toycoronagraph.sources

"""
Source modules: each input of a scene (an image, an MCFOST model, a stellar wind, a CME,
planets, or your own module) is one class following the Source interface.

A module declares, as class attributes:
    kind      the name used in the parameter file ([[source]] kind = "...")
    doc       one line describing it
    family    "physical" (e.g. Jy/arcsec^2), "relative" (contrast: a fraction of the star's
              flux) or None (decided by the input, e.g. a FITS file's BUNIT)
    params    its own parameters (toycoronagraph.params.Param); shared quantities such as
              the star's distance are never repeated here, they are listed in `requires`
    requires  the shared quantities it needs, e.g. ("star.distance", "star.radius")
    provides  the shared quantities it can supply, e.g. ("grid.pixel_scale",) from a header

and implements render(grid, star), returning its image centred on the star (pixel (N-1)/2 of
the grid, or of its own grid with pixel_scale set); a module that wraps another code makes
sure of this itself. Register a module with @register_source; a module outside the package
is found from the parameter file's `module = "my_package.my_module"` (imported, which
registers it). tests/extra_sources.py (CmeBlob) is a documented example.
"""
import importlib

_SOURCES = {}  # registry: kind -> Source subclass (filled by @register_source)


[docs] class Source: """ Base class of the source modules (see the module docstring). Attributes: kind (str): name in parameter files; must be set by every registered subclass. doc (str): one-line description. family (str): "physical", "relative", or None when the input decides. params (tuple of Param): the module's own parameters. requires (tuple of str): shared quantities it needs, e.g. "star.distance". provides (tuple of str): shared quantities it can supply (reported by inspect()). settings (dict): {param name: value} of every parameter, defaults filled in (set by __init__). """ kind = None doc = "" family = None params = () requires = () provides = () def __init__(self, **settings): """ Check and store the module's own parameters. Args: **settings: {param name: value}; each is converted with toycoronagraph.params.convert (units in text, "auto", choices...); parameters not given take their default. Raises: ParamError: unknown parameter names (with a suggestion) or invalid values, all listed. """ from toycoronagraph.params import convert, ParamError import difflib known = {p.name: p for p in self.params} errors, values = [], {} for key, value in settings.items(): if key not in known: close = difflib.get_close_matches(key, list(known), 1, 0.6) errors.append("%s: unknown parameter %s%s" % (self.kind, key, (" (did you mean %s?)" % close[0]) if close else "; known: " + ", ".join(known))) continue try: values[key] = convert(known[key], value, self.kind) except ValueError as err: errors.append(str(err)) for p in self.params: if p.name not in values: values[p.name] = p.default if errors: raise ParamError("\n".join(errors)) self.settings = values
[docs] def missing(self): """ Required parameters (no default) that are not set. Returns: list of Param: empty when the module can be rendered. """ return [p for p in self.params if self.settings.get(p.name) is None and p.default is None]
[docs] def family_of(self): """ The family of this source's images, given its settings. Returns: str: "physical", "relative", or None when only the input decides (e.g. a file's BUNIT). """ return self.family
[docs] def inspect(self): """ Shared quantities this source supplies (e.g. from a file header); see `provides`. Returns: list of tuple: [(key, value, origin)], e.g. ("grid.pixel_scale", 0.01, "header PIXSCALE of x.fits"); empty by default. """ return []
[docs] def point_sources(self, grid, star): """ Unresolved sources, for modules that are not images (render() is then not called). Args: grid (toycoronagraph.scene.Grid): the scene grid. star (toycoronagraph.scene.Star): the star. Returns: list or None: [((radius arcsec, angle rad), flux)], the flux in Jy (family "physical") or as a fraction of the star's flux ("relative"); None for image modules (default). """ return None
[docs] def render(self, grid, star): """ The image(s) of this source; every image module overrides it. Args: grid (toycoronagraph.scene.Grid): the scene grid (size, pixel_scale arcsec, wavelength m). star (toycoronagraph.scene.Star): the star (distance pc, radius R_sun, temperature K, flux_jy). Returns: SourceImage or dict: a units.SourceImage centred on the star, or {name: SourceImage} for several components. Raises: NotImplementedError: in the base class. """ raise NotImplementedError("%s: rendering is added in a later stage" % self.kind)
[docs] def symmetric(self): """ Whether the image is circularly symmetric, from the "symmetric" setting if the module has one. Returns: bool or None: True / False if known (by construction or set by the user); None: measure it. """ value = self.settings.get("symmetric", "auto") return None if value == "auto" else bool(value)
[docs] def register_source(cls): """ Class decorator: make a Source subclass available under its kind. Args: cls (type): the Source subclass; registering a kind again replaces the previous class. Returns: type: cls itself. Raises: ValueError: no kind, or two parameters with the same name. """ if not cls.kind: raise ValueError("%s has no kind" % cls.__name__) names = [p.name for p in cls.params] if len(set(names)) != len(names): raise ValueError("%s: duplicate parameter names" % cls.kind) _SOURCES[cls.kind] = cls return cls
[docs] def get_source(kind, module=None): """ The Source class of a kind. Args: kind (str): the registered kind, e.g. "wind". module (str): Python module to import first if the kind is not registered yet (importing it registers its classes); None = none. Returns: type: the Source subclass. Raises: KeyError: the kind is still not registered. """ _load_builtins() if kind not in _SOURCES and module: importlib.import_module(module) if kind not in _SOURCES: raise KeyError(kind) return _SOURCES[kind]
BUILTIN_KINDS = ("fits_image", "mcfost", "scene_fits", "wind", "cme", "planet")
[docs] def source_kinds(): """ All registered kinds. Returns: list of str: the built-in ones first (in the order of BUILTIN_KINDS), then yours (in registration order). """ _load_builtins() return [k for k in BUILTIN_KINDS if k in _SOURCES] + [k for k in _SOURCES if k not in BUILTIN_KINDS]
def _load_builtins(): """Import the built-in source modules, which registers them (imported on first use, not with the package).""" from toycoronagraph.sources import images, thomson, cme, planets # noqa: F401 (registers them)