Coverage for python / lsst / images / fields / _base.py: 56%
49 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 09:16 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 09:16 +0000
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14__all__ = ("BaseField",)
16from abc import ABC, abstractmethod
17from typing import Literal, Self, overload
19import astropy.units
20import numpy as np
22from .._geom import Bounds, Box
23from .._image import Image
26class BaseField(ABC):
27 """An abstract base class for parametric or interpolated 2-d functions,
28 generally representing some sort of calculated image.
30 Notes
31 -----
32 The field hierarchy is closed to the types in this package, so we can
33 enumerate all of the serializations and avoid any kind of extension system.
34 All field types are immutable.
36 Field types implement the function call operator and both multiplication
37 and division by a constant via operator overloading. See the named
38 `evaluate` and `multiply_constant` methods (respectively) for more
39 information about those operations.
41 This interface will probably change in the future to incorporate options
42 for dealing with out-of-bounds positions. At present the behavior for
43 such positions is implementation-specific and should not be relied upon.
44 """
46 @property
47 @abstractmethod
48 def bounds(self) -> Bounds:
49 """The region over which this field can be evaluated (`.Bounds`)."""
50 raise NotImplementedError()
52 @property
53 @abstractmethod
54 def unit(self) -> astropy.units.UnitBase | None:
55 """The units of the field (`astropy.units.UnitBase` or `None`)."""
56 raise NotImplementedError()
58 @overload
59 def __call__(self, *, x: np.ndarray, y: np.ndarray, quantity: Literal[False] = False) -> np.ndarray: ... 59 ↛ exitline 59 didn't return from function '__call__' because
61 @overload
62 def __call__( 62 ↛ exitline 62 didn't return from function '__call__' because
63 self, *, x: np.ndarray, y: np.ndarray, quantity: Literal[True]
64 ) -> astropy.units.Quantity: ...
66 @overload
67 def __call__( 67 ↛ exitline 67 didn't return from function '__call__' because
68 self, *, x: np.ndarray, y: np.ndarray, quantity: bool
69 ) -> np.ndarray | astropy.units.Quantity: ...
71 def __call__(
72 self, *, x: np.ndarray, y: np.ndarray, quantity: bool = False
73 ) -> np.ndarray | astropy.units.Quantity:
74 return self.evaluate(x=x, y=y, quantity=quantity)
76 @abstractmethod
77 def render(
78 self,
79 bbox: Box | None = None,
80 *,
81 dtype: np.typing.DTypeLike | None = None,
82 ) -> Image:
83 """Create an image realization of the field.
85 Parameters
86 ----------
87 bbox
88 Bounding box of the image. If not provided, ``self.bounds.bbox``
89 will be used.
90 dtype
91 Pixel data type for the returned image.
92 """
93 raise NotImplementedError()
95 def __mul__(self, factor: float | astropy.units.Quantity | astropy.units.UnitBase) -> Self:
96 return self.multiply_constant(factor)
98 def __rmul__(self, factor: float | astropy.units.Quantity | astropy.units.UnitBase) -> Self:
99 return self.multiply_constant(factor)
101 def __truediv__(self, factor: float | astropy.units.Quantity | astropy.units.UnitBase) -> Self:
102 return self.multiply_constant(1.0 / factor)
104 @abstractmethod
105 def evaluate(
106 self, *, x: np.ndarray, y: np.ndarray, quantity: bool
107 ) -> np.ndarray | astropy.units.Quantity:
108 """Evaluate at non-gridded points.
110 Parameters
111 ----------
112 x
113 X coordinates to evaluate at.
114 y
115 Y coordinates to evaluate at; must be broadcast-compatible with
116 ``x``.
117 quantity
118 If `True`, return an `astropy.units.Quantity` instead of a
119 `numpy.ndarray`. If `unit` is `None`, the returned object will
120 be a dimensionless `~astropy.units.Quantity`.
121 """
122 raise NotImplementedError()
124 @abstractmethod
125 def multiply_constant(self, factor: float | astropy.units.Quantity | astropy.units.UnitBase) -> Self:
126 """Multiply by a constant, returning a new field of the same type.
128 Parameters
129 ----------
130 factor
131 Factor to multiply by. When this has units, those should multiply
132 ``self.unit`` or set the units of the returned field if
133 ``self.unit is None``.
134 """
135 raise NotImplementedError()
137 def _handle_factor_units(
138 self, factor: float | astropy.units.Quantity | astropy.units.UnitBase
139 ) -> tuple[float, astropy.units.UnitBase | None]:
140 """Interpret the ``factor`` argument to `multiply_constant` and apply
141 any units it carries to this field's units.
143 This is a convenience function for subclass implementations of
144 `multiply_constant`.
146 Parameters
147 ----------
148 factor
149 Factor passed by the caller.
151 Returns
152 -------
153 `float`
154 The factor to multiply by as a pure `float`
155 `astropy.units.UnitBase` | `None`
156 The units for the new field returned by `multiply_constant`.
157 """
158 unit = self.unit
159 factor_unit = None
160 if isinstance(factor, astropy.units.Quantity):
161 factor_unit = factor.unit
162 factor = factor.to_value()
163 elif isinstance(factor, astropy.units.UnitBase):
164 factor_unit = factor
165 factor = 1.0
166 if factor_unit is not None:
167 if unit is None:
168 unit = factor_unit
169 else:
170 unit *= factor_unit
171 return factor, unit