Coverage for python / lsst / images / serialization / _asdf_utils.py: 78%
102 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 08:34 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 08:34 +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 "ArrayReferenceModel",
16 "ArrayReferenceQuantityModel",
17 "InlineArray",
18 "InlineArrayModel",
19 "InlineArrayQuantity",
20 "InlineArrayQuantityModel",
21 "Quantity",
22 "QuantityModel",
23 "Time",
24 "TimeModel",
25 "Unit",
26)
28from typing import Annotated, Any, ClassVar, Literal
30import astropy.time
31import astropy.units
32import numpy as np
33import pydantic
34import pydantic_core.core_schema as pcs
36from ._dtypes import NumberType
39class _UnitSerialization:
40 """Pydantic hooks for unit serialization.
42 This class provides implementations for the `Unit` type alias for
43 `astropy.unit.Unit` that adds Pydantic serialization and validation.
44 """
46 @classmethod
47 def __get_pydantic_core_schema__(
48 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
49 ) -> pcs.CoreSchema:
50 from_str_schema = pcs.chain_schema(
51 [
52 pcs.str_schema(),
53 pcs.no_info_plain_validator_function(cls.from_str),
54 ]
55 )
56 return pcs.json_or_python_schema(
57 json_schema=from_str_schema,
58 python_schema=pcs.union_schema([pcs.is_instance_schema(astropy.units.UnitBase), from_str_schema]),
59 serialization=pcs.plain_serializer_function_ser_schema(cls.to_str),
60 )
62 @classmethod
63 def from_str(cls, value: str) -> astropy.units.UnitBase:
64 return astropy.units.Unit(value, format="vounit")
66 @staticmethod
67 def to_str(unit: astropy.units.UnitBase) -> str:
68 return unit.to_string("vounit")
71type Unit = Annotated[
72 astropy.units.UnitBase,
73 _UnitSerialization,
74 pydantic.WithJsonSchema(
75 {
76 "type": "string",
77 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
78 "id": "http://stsci.edu/schemas/asdf/unit/unit-1.0.0",
79 "tag": "!unit/unit-1.0.0",
80 }
81 ),
82]
85class ArrayReferenceModel(pydantic.BaseModel, ser_json_inf_nan="constants"):
86 """Model for the subset of the ASDF 'ndarray' schema, in the case where the
87 array data is stored elsewhere.
88 """
90 source: str | int
91 shape: list[int]
92 datatype: NumberType
93 byteorder: Literal["big"] = "big"
95 model_config = pydantic.ConfigDict(
96 json_schema_extra={
97 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
98 "id": "http://stsci.edu/schemas/asdf/core/ndarray-1.1.0",
99 "tag": "!core/ndarray-1.1.0",
100 }
101 )
103 source_is_table: ClassVar[Literal[False]] = False
106class InlineArrayModel(pydantic.BaseModel, ser_json_inf_nan="constants"):
107 """Model for the subset of the ASDF 'ndarray' schema, in the case where the
108 array data is stored inline.
109 """
111 data: list[Any]
112 datatype: NumberType
114 model_config = pydantic.ConfigDict(
115 json_schema_extra={
116 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
117 "id": "http://stsci.edu/schemas/asdf/core/ndarray-1.1.0",
118 "tag": "!core/ndarray-1.1.0",
119 }
120 )
123class _InlineArraySerialization:
124 """Pydantic hooks for array serialization.
126 This class provides implementations for the `Array` type alias for
127 `numpy.ndarray` that adds Pydantic serialization and validation.
128 """
130 @classmethod
131 def __get_pydantic_core_schema__(
132 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
133 ) -> pcs.CoreSchema:
134 from_model_schema = pcs.chain_schema(
135 [
136 handler(InlineArrayModel),
137 pcs.no_info_plain_validator_function(cls.from_model),
138 ]
139 )
140 return pcs.json_or_python_schema(
141 json_schema=from_model_schema,
142 python_schema=pcs.union_schema([pcs.is_instance_schema(np.ndarray), from_model_schema]),
143 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model),
144 )
146 @classmethod
147 def from_model(cls, model: InlineArrayModel) -> np.ndarray:
148 return np.array(model.data, dtype=model.datatype.to_numpy())
150 @classmethod
151 def to_model(cls, array: np.ndarray) -> InlineArrayModel:
152 datatype = NumberType.from_numpy(array.dtype)
153 return InlineArrayModel(data=array.tolist(), datatype=datatype)
156type InlineArray = Annotated[np.ndarray, _InlineArraySerialization]
159class QuantityModel(pydantic.BaseModel, ser_json_inf_nan="constants"):
160 """Model for a subset of the ASDF 'quantity' schema for scalars."""
162 value: pydantic.StrictFloat
163 unit: Unit
165 model_config = pydantic.ConfigDict(
166 json_schema_extra={
167 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
168 "id": "http://stsci.edu/schemas/asdf/unit/quantity-1.2.0",
169 "tag": "!unit/quantity-1.2.0",
170 }
171 )
174class InlineArrayQuantityModel(pydantic.BaseModel, ser_json_inf_nan="constants"):
175 """Model for a subset of the ASDF 'quantity' schema for inline arrays."""
177 value: InlineArrayModel
178 unit: Unit
180 model_config = pydantic.ConfigDict(
181 json_schema_extra={
182 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
183 "id": "http://stsci.edu/schemas/asdf/unit/quantity-1.2.0",
184 "tag": "!unit/quantity-1.2.0",
185 }
186 )
189class ArrayReferenceQuantityModel(pydantic.BaseModel, ser_json_inf_nan="constants"):
190 """Model for a subset of the ASDF 'quantity' schema for external arrays."""
192 value: ArrayReferenceModel
193 unit: Unit
195 model_config = pydantic.ConfigDict(
196 json_schema_extra={
197 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
198 "id": "http://stsci.edu/schemas/asdf/unit/quantity-1.2.0",
199 "tag": "!unit/quantity-1.2.0",
200 }
201 )
204class _QuantitySerialization:
205 """Pydantic hooks for scalar quantity serialization."""
207 @classmethod
208 def __get_pydantic_core_schema__(
209 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
210 ) -> pcs.CoreSchema:
211 from_model_schema = pcs.chain_schema(
212 [
213 handler(QuantityModel),
214 pcs.no_info_plain_validator_function(cls.from_model),
215 ]
216 )
217 return pcs.json_or_python_schema(
218 json_schema=from_model_schema,
219 python_schema=pcs.union_schema(
220 [pcs.is_instance_schema(astropy.units.Quantity), from_model_schema]
221 ),
222 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model),
223 )
225 @classmethod
226 def from_model(cls, model: QuantityModel) -> astropy.units.Quantity:
227 return astropy.units.Quantity(model.value, unit=model.unit)
229 @classmethod
230 def to_model(cls, quantity: astropy.units.Quantity) -> QuantityModel:
231 assert quantity.isscalar
232 return QuantityModel(value=quantity.to_value(), unit=_UnitSerialization.to_str(quantity.unit))
235type Quantity = Annotated[astropy.units.Quantity, _QuantitySerialization]
238class _InlineArrayQuantitySerialization:
239 """Pydantic hooks for inline array quantity serialization."""
241 @classmethod
242 def __get_pydantic_core_schema__(
243 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
244 ) -> pcs.CoreSchema:
245 from_model_schema = pcs.chain_schema(
246 [
247 handler(InlineArrayQuantityModel),
248 pcs.no_info_plain_validator_function(cls.from_model),
249 ]
250 )
251 return pcs.json_or_python_schema(
252 json_schema=from_model_schema,
253 python_schema=pcs.union_schema(
254 [pcs.is_instance_schema(astropy.units.Quantity), from_model_schema]
255 ),
256 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model),
257 )
259 @classmethod
260 def from_model(cls, model: InlineArrayQuantityModel) -> astropy.units.Quantity:
261 return astropy.units.Quantity(_InlineArraySerialization.from_model(model.value), unit=model.unit)
263 @classmethod
264 def to_model(cls, quantity: astropy.units.Quantity) -> InlineArrayQuantityModel:
265 assert quantity.isscalar
266 return InlineArrayQuantityModel(
267 value=_InlineArraySerialization.to_model(quantity.to_value()),
268 unit=_UnitSerialization.to_str(quantity.unit),
269 )
272type InlineArrayQuantity = Annotated[astropy.units.Quantity, _InlineArrayQuantitySerialization]
275class TimeModel(pydantic.BaseModel, ser_json_inf_nan="constants"):
276 """Model for a subset of the ASDF 'time' schema."""
278 value: str
279 scale: Literal["utc", "tai"]
280 format: Literal["iso"] = "iso"
282 model_config = pydantic.ConfigDict(
283 json_schema_extra={
284 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
285 "id": "http://stsci.edu/schemas/asdf/time/time-1.2.0",
286 "tag": "!time/time-1.2.0",
287 }
288 )
291class _TimeSerialization:
292 """Pydantic hooks for time serialization.
294 This class provides implementations for the `Time` type alias for
295 `astropy.time.Time` that adds Pydantic serialization and validation.
296 """
298 @classmethod
299 def __get_pydantic_core_schema__(
300 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
301 ) -> pcs.CoreSchema:
302 from_model_schema = pcs.chain_schema(
303 [
304 TimeModel.__pydantic_core_schema__,
305 pcs.no_info_plain_validator_function(cls.from_model),
306 ]
307 )
308 return pcs.json_or_python_schema(
309 json_schema=from_model_schema,
310 python_schema=pcs.union_schema([pcs.is_instance_schema(astropy.time.Time), from_model_schema]),
311 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model, info_arg=False),
312 )
314 @classmethod
315 def from_model(cls, model: TimeModel) -> astropy.time.Time:
316 return astropy.time.Time(model.value, scale=model.scale, format=model.format)
318 @classmethod
319 def to_model(cls, time: astropy.time.Time) -> TimeModel:
320 if time.scale != "utc" and time.scale != "tai":
321 time = time.tai
322 return TimeModel(value=time.to_value("iso"), scale=time.scale, format="iso")
325type Time = Annotated[astropy.time.Time, _TimeSerialization]