Coverage for python / lsst / images / serialization / _asdf_utils.py: 78%
103 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-23 22:23 -0700
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-23 22:23 -0700
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 ._common import ArchiveTree
37from ._dtypes import NumberType
40class _UnitSerialization:
41 """Pydantic hooks for unit serialization.
43 This class provides implementations for the `Unit` type alias for
44 `astropy.unit.Unit` that adds Pydantic serialization and validation.
45 """
47 @classmethod
48 def __get_pydantic_core_schema__(
49 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
50 ) -> pcs.CoreSchema:
51 from_str_schema = pcs.chain_schema(
52 [
53 pcs.str_schema(),
54 pcs.no_info_plain_validator_function(cls.from_str),
55 ]
56 )
57 return pcs.json_or_python_schema(
58 json_schema=from_str_schema,
59 python_schema=pcs.union_schema([pcs.is_instance_schema(astropy.units.UnitBase), from_str_schema]),
60 serialization=pcs.plain_serializer_function_ser_schema(cls.to_str),
61 )
63 @classmethod
64 def from_str(cls, value: str) -> astropy.units.UnitBase:
65 return astropy.units.Unit(value, format="vounit")
67 @staticmethod
68 def to_str(unit: astropy.units.UnitBase) -> str:
69 return unit.to_string("vounit")
72type Unit = Annotated[
73 astropy.units.UnitBase,
74 _UnitSerialization,
75 pydantic.WithJsonSchema(
76 {
77 "type": "string",
78 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
79 "id": "http://stsci.edu/schemas/asdf/unit/unit-1.0.0",
80 "tag": "!unit/unit-1.0.0",
81 }
82 ),
83]
86class ArrayReferenceModel(ArchiveTree):
87 """Model for the subset of the ASDF 'ndarray' schema, in the case where the
88 array data is stored elsewhere.
89 """
91 source: str | int
92 shape: list[int]
93 datatype: NumberType
94 byteorder: Literal["big"] = "big"
96 model_config = pydantic.ConfigDict(
97 json_schema_extra={
98 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
99 "id": "http://stsci.edu/schemas/asdf/core/ndarray-1.1.0",
100 "tag": "!core/ndarray-1.1.0",
101 }
102 )
104 source_is_table: ClassVar[Literal[False]] = False
107class InlineArrayModel(ArchiveTree):
108 """Model for the subset of the ASDF 'ndarray' schema, in the case where the
109 array data is stored inline.
110 """
112 data: list[Any]
113 datatype: NumberType
115 model_config = pydantic.ConfigDict(
116 json_schema_extra={
117 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
118 "id": "http://stsci.edu/schemas/asdf/core/ndarray-1.1.0",
119 "tag": "!core/ndarray-1.1.0",
120 }
121 )
124class _InlineArraySerialization:
125 """Pydantic hooks for array serialization.
127 This class provides implementations for the `Array` type alias for
128 `numpy.ndarray` that adds Pydantic serialization and validation.
129 """
131 @classmethod
132 def __get_pydantic_core_schema__(
133 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
134 ) -> pcs.CoreSchema:
135 from_model_schema = pcs.chain_schema(
136 [
137 handler(InlineArrayModel),
138 pcs.no_info_plain_validator_function(cls.from_model),
139 ]
140 )
141 return pcs.json_or_python_schema(
142 json_schema=from_model_schema,
143 python_schema=pcs.union_schema([pcs.is_instance_schema(np.ndarray), from_model_schema]),
144 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model),
145 )
147 @classmethod
148 def from_model(cls, model: InlineArrayModel) -> np.ndarray:
149 return np.array(model.data, dtype=model.datatype.to_numpy())
151 @classmethod
152 def to_model(cls, array: np.ndarray) -> InlineArrayModel:
153 datatype = NumberType.from_numpy(array.dtype)
154 return InlineArrayModel(data=array.tolist(), datatype=datatype)
157type InlineArray = Annotated[np.ndarray, _InlineArraySerialization]
160class QuantityModel(ArchiveTree):
161 """Model for a subset of the ASDF 'quantity' schema for scalars."""
163 value: pydantic.StrictFloat
164 unit: Unit
166 model_config = pydantic.ConfigDict(
167 json_schema_extra={
168 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
169 "id": "http://stsci.edu/schemas/asdf/unit/quantity-1.2.0",
170 "tag": "!unit/quantity-1.2.0",
171 }
172 )
175class InlineArrayQuantityModel(ArchiveTree):
176 """Model for a subset of the ASDF 'quantity' schema for inline arrays."""
178 value: InlineArrayModel
179 unit: Unit
181 model_config = pydantic.ConfigDict(
182 json_schema_extra={
183 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
184 "id": "http://stsci.edu/schemas/asdf/unit/quantity-1.2.0",
185 "tag": "!unit/quantity-1.2.0",
186 }
187 )
190class ArrayReferenceQuantityModel(ArchiveTree):
191 """Model for a subset of the ASDF 'quantity' schema for external arrays."""
193 value: ArrayReferenceModel
194 unit: Unit
196 model_config = pydantic.ConfigDict(
197 json_schema_extra={
198 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
199 "id": "http://stsci.edu/schemas/asdf/unit/quantity-1.2.0",
200 "tag": "!unit/quantity-1.2.0",
201 }
202 )
205class _QuantitySerialization:
206 """Pydantic hooks for scalar quantity serialization."""
208 @classmethod
209 def __get_pydantic_core_schema__(
210 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
211 ) -> pcs.CoreSchema:
212 from_model_schema = pcs.chain_schema(
213 [
214 handler(QuantityModel),
215 pcs.no_info_plain_validator_function(cls.from_model),
216 ]
217 )
218 return pcs.json_or_python_schema(
219 json_schema=from_model_schema,
220 python_schema=pcs.union_schema(
221 [pcs.is_instance_schema(astropy.units.Quantity), from_model_schema]
222 ),
223 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model),
224 )
226 @classmethod
227 def from_model(cls, model: QuantityModel) -> astropy.units.Quantity:
228 return astropy.units.Quantity(model.value, unit=model.unit)
230 @classmethod
231 def to_model(cls, quantity: astropy.units.Quantity) -> QuantityModel:
232 assert quantity.isscalar
233 return QuantityModel(value=quantity.to_value(), unit=_UnitSerialization.to_str(quantity.unit))
236type Quantity = Annotated[astropy.units.Quantity, _QuantitySerialization]
239class _InlineArrayQuantitySerialization:
240 """Pydantic hooks for inline array quantity serialization."""
242 @classmethod
243 def __get_pydantic_core_schema__(
244 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
245 ) -> pcs.CoreSchema:
246 from_model_schema = pcs.chain_schema(
247 [
248 handler(InlineArrayQuantityModel),
249 pcs.no_info_plain_validator_function(cls.from_model),
250 ]
251 )
252 return pcs.json_or_python_schema(
253 json_schema=from_model_schema,
254 python_schema=pcs.union_schema(
255 [pcs.is_instance_schema(astropy.units.Quantity), from_model_schema]
256 ),
257 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model),
258 )
260 @classmethod
261 def from_model(cls, model: InlineArrayQuantityModel) -> astropy.units.Quantity:
262 return astropy.units.Quantity(_InlineArraySerialization.from_model(model.value), unit=model.unit)
264 @classmethod
265 def to_model(cls, quantity: astropy.units.Quantity) -> InlineArrayQuantityModel:
266 assert quantity.isscalar
267 return InlineArrayQuantityModel(
268 value=_InlineArraySerialization.to_model(quantity.to_value()),
269 unit=_UnitSerialization.to_str(quantity.unit),
270 )
273type InlineArrayQuantity = Annotated[astropy.units.Quantity, _InlineArrayQuantitySerialization]
276class TimeModel(ArchiveTree):
277 """Model for a subset of the ASDF 'time' schema."""
279 value: str
280 scale: Literal["utc", "tai"]
281 format: Literal["iso"] = "iso"
283 model_config = pydantic.ConfigDict(
284 json_schema_extra={
285 "$schema": "http://stsci.edu/schemas/yaml-schema/draft-01",
286 "id": "http://stsci.edu/schemas/asdf/time/time-1.2.0",
287 "tag": "!time/time-1.2.0",
288 }
289 )
292class _TimeSerialization:
293 """Pydantic hooks for time serialization.
295 This class provides implementations for the `Time` type alias for
296 `astropy.time.Time` that adds Pydantic serialization and validation.
297 """
299 @classmethod
300 def __get_pydantic_core_schema__(
301 cls, source_type: Any, handler: pydantic.GetCoreSchemaHandler
302 ) -> pcs.CoreSchema:
303 from_model_schema = pcs.chain_schema(
304 [
305 TimeModel.__pydantic_core_schema__,
306 pcs.no_info_plain_validator_function(cls.from_model),
307 ]
308 )
309 return pcs.json_or_python_schema(
310 json_schema=from_model_schema,
311 python_schema=pcs.union_schema([pcs.is_instance_schema(astropy.time.Time), from_model_schema]),
312 serialization=pcs.plain_serializer_function_ser_schema(cls.to_model, info_arg=False),
313 )
315 @classmethod
316 def from_model(cls, model: TimeModel) -> astropy.time.Time:
317 return astropy.time.Time(model.value, scale=model.scale, format=model.format)
319 @classmethod
320 def to_model(cls, time: astropy.time.Time) -> TimeModel:
321 if time.scale != "utc" and time.scale != "tai":
322 time = time.tai
323 return TimeModel(value=time.to_value("iso"), scale=time.scale, format="iso")
326type Time = Annotated[astropy.time.Time, _TimeSerialization]