Source code for spindoctor.sim.forward.ghosts

"""Ghost reflections for the optics stage.

A ghost is a faint internal reflection of the focal-plane image: a displaced,
defocused, low-amplitude copy of the scene added back onto it.  A bright star
casts a ghost the same way an extended source does, so each ghost reflects both
the intensive signal (bodies, rings, sky) and the point-source plane (stars).
Each ghost copies the pre-ghost planes (so ghosts do not reflect one another),
shifts them by its offset, blurs them by its defocus, scales them by its
amplitude, and adds them back in.
"""

from collections.abc import Mapping, Sequence
from typing import Any

from scipy import ndimage

from spindoctor.sim.forward.stages import SimFrame
from spindoctor.support.types import NDArrayFloatType

__all__ = ['apply_ghosts']


def _add_ghosts_to_plane(
    plane: NDArrayFloatType,
    ghosts: Sequence[Mapping[str, Any]],
    oversample: int,
) -> None:
    """Add every ghost's displaced, defocused, scaled copy to one plane in place."""
    if not plane.any():
        return
    pre_ghost = plane.copy()
    for ghost in ghosts:
        amplitude = float(ghost.get('amplitude', 0.0))
        if amplitude == 0.0:
            continue
        dv = float(ghost.get('dv_px', 0.0)) * oversample
        du = float(ghost.get('du_px', 0.0)) * oversample
        defocus_sigma = float(ghost.get('defocus_sigma', 0.0)) * oversample
        copy = ndimage.shift(pre_ghost, (dv, du), order=1, mode='constant', cval=0.0)
        if defocus_sigma > 0.0:
            copy = ndimage.gaussian_filter(copy, defocus_sigma, mode='constant')
        plane += amplitude * copy


[docs] def apply_ghosts( frame: SimFrame, *, ghosts: Sequence[Mapping[str, Any]], oversample: int, ) -> None: """Add displaced, defocused, scaled copies of the scene in place. Parameters: frame: The frame whose signal and point-source planes receive the ghosts. ghosts: The scene ``optics.ghosts`` list of ghost specifications. oversample: The render-grid oversampling factor (offsets and defocus are in detector pixels and scale to the render grid). """ if not ghosts: return _add_ghosts_to_plane(frame.signal, ghosts, oversample) _add_ghosts_to_plane(frame.point_e, ghosts, oversample)