Coverage for python/lsst/images/fields/_concrete.py: 35%
39 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 08:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 08:40 +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__ = (
15 "Field",
16 "FieldSerializationModel",
17 "field_from_legacy",
18 "field_from_legacy_background",
19 "field_from_legacy_photo_calib",
20)
22from typing import TYPE_CHECKING, Annotated, Any
24import astropy.units
25import numpy as np
26import pydantic
28from .._geom import Bounds
29from ._chebyshev import ChebyshevField, ChebyshevFieldSerializationModel
30from ._product import ProductField, ProductFieldSerializationModel
31from ._spline import SplineField, SplineFieldSerializationModel
32from ._sum import SumField, SumFieldSerializationModel
34if TYPE_CHECKING:
35 try:
36 from lsst.afw.image import PhotoCalib as LegacyPhotoCalib
37 from lsst.afw.math import BackgroundList as LegacyBackgroundList
38 from lsst.afw.math import BackgroundMI as LegacyBackground
39 from lsst.afw.math import BoundedField as LegacyBoundedField
40 except ImportError:
41 type LegacyBoundedField = Any # type: ignore[no-redef]
42 type LegacyBackground = Any # type: ignore[no-redef]
43 type LegacyBackgroundList = Any # type: ignore[no-redef]
44 type LegacyPhotoCalib = Any # type: ignore[no-redef]
47# Since Sphinx can't handle doc links to type aliases, whenever we annotate
48# a type as `Field`, we override the docs to say `BaseField`, since
49# `BaseField` is a base class that serves as a much more useful doc link, and
50# because the hierarchy is closed they're equivalent. But we have to use
51# `Field` in the type annotations because there's no way to declare to MyPy
52# et all that the hierarchy is closed.
54type Field = ChebyshevField | ProductField | SplineField | SumField
55type FieldSerializationModel = Annotated[
56 ChebyshevFieldSerializationModel
57 | ProductFieldSerializationModel
58 | SplineFieldSerializationModel
59 | SumFieldSerializationModel,
60 pydantic.Field(discriminator="field_type"),
61]
64ProductFieldSerializationModel.model_rebuild()
65SumFieldSerializationModel.model_rebuild()
68def field_from_legacy(
69 legacy_bounded_field: LegacyBoundedField,
70 bounds: Bounds | None = None,
71 unit: astropy.units.UnitBase | None = None,
72) -> Field:
73 """Convert a legacy `lsst.afw.math.BoundedField` subclass to a `BaseField`
74 object.
76 Parameters
77 ----------
78 legacy_bounded_field
79 Legacy field to convert.
80 bounds
81 The bounds of the returned field, if they should be different from
82 the bounding box of ``legacy``.
83 unit
84 The units of the returned field (`lsst.afw.math.BoundedField`
85 objects do not know their units).
86 """
87 from lsst.afw.math import ChebyshevBoundedField, ProductBoundedField
89 match legacy_bounded_field:
90 case ChebyshevBoundedField():
91 return ChebyshevField.from_legacy(legacy_bounded_field, unit=unit, bounds=bounds)
92 case ProductBoundedField():
93 return ProductField.from_legacy(legacy_bounded_field, unit=unit, bounds=bounds)
94 case _:
95 raise NotImplementedError(
96 f"Conversion from {type(legacy_bounded_field).__name__} is not supported."
97 )
100def field_from_legacy_background(
101 legacy_background: LegacyBackground | LegacyBackgroundList,
102 bounds: Bounds | None = None,
103 unit: astropy.units.UnitBase | None = None,
104) -> Field:
105 """Convert a legacy `lsst.afw.math.Background` or
106 `lsst.afw.math.BackgroundList` instance to a `BaseField` object.
108 Parameters
109 ----------
110 legacy_background
111 Legacy background object to convert.
112 bounds
113 The bounds of the returned field, if they should be different from
114 the bounding box of ``legacy_background``.
115 unit
116 The units of the returned field (`lsst.afw.math.Background`
117 objects do not know their units).
118 """
119 from lsst.afw.math import ApproximateControl, BackgroundList
121 if isinstance(legacy_background, BackgroundList):
122 return SumField.from_legacy_background(legacy_background)
124 approx_control = legacy_background.getBackgroundControl().getApproximateControl()
125 if approx_control.getStyle() == ApproximateControl.UNKNOWN:
126 return SplineField.from_legacy_background(legacy_background, unit=unit)
127 else:
128 return ChebyshevField.from_legacy_background(legacy_background, unit=unit)
131def field_from_legacy_photo_calib(
132 legacy_photo_calib: LegacyPhotoCalib,
133 bounds: Bounds,
134 instrumental_unit: astropy.units.UnitBase = astropy.units.electron,
135) -> Field | None:
136 """Convert a legacy `lsst.afw.image.PhotoCalib` into a `BaseField` object.
138 Parameters
139 ----------
140 legacy_photo_calib
141 Calibration object to convert.
142 bounds
143 Bounds of the returned field.
144 instrumental_unit
145 The instrumental units the legacy calibration transforms from. These
146 will be used as the denominator of the units of the returned field,
147 with ``astropy.units.nJy`` as the numerator.
149 Returns
150 -------
151 `BaseField` | `None`
152 A field that transforms instrumental units to ``nJy``, or `None` if
153 the given calibration object was an identity mapping for a legacy
154 image that already had ``nJy`` pixels.
155 """
156 calibration_mean = legacy_photo_calib.getCalibrationMean()
157 if legacy_photo_calib._isConstant:
158 if calibration_mean == 1.0:
159 # This image's pixels have been calibrated to nJy
160 # already, which means the calibration *from* post-ISR
161 # electrons that we want is stored elsewhere.
162 return None
163 else:
164 return ChebyshevField(
165 bounds,
166 np.array([[calibration_mean]], dtype=np.float64),
167 unit=astropy.units.nJy / instrumental_unit,
168 )
169 else:
170 normalized_field = field_from_legacy(
171 legacy_photo_calib.computeScaledCalibration(),
172 unit=astropy.units.nJy / instrumental_unit,
173 bounds=bounds,
174 )
175 return normalized_field * calibration_mean