Coverage for python / lsst / images / _transforms / _ast.py: 12%
216 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 02:28 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 02:28 -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
14from collections.abc import Iterable
15from typing import Any
17__all__ = (
18 "USING_STARLINK_PYAST",
19 "FitsChan",
20 "Frame",
21 "FrameSet",
22 "Mapping",
23 "ShiftMap",
24 "SkyFrame",
25 "StringStream",
26 "UnitMap",
27 "wrap_frame_set",
28 "wrap_mapping",
29)
31try:
32 import starlink.Ast as StarAst
33except ImportError:
34 StarAst = None
35 import astshim as AstShim
37 USING_STARLINK_PYAST = False
38else:
39 AstShim = None
40 USING_STARLINK_PYAST = True
42# Public backend-dependent bindings.
43StringStream: Any
44FitsChan: Any
45Mapping: Any
46UnitMap: Any
47ShiftMap: Any
48Frame: Any
49SkyFrame: Any
50FrameSet: Any
53if USING_STARLINK_PYAST: 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true
54 assert StarAst is not None
56 class _StringStream:
57 """A source/sink object compatible with starlink.Ast channel APIs."""
59 def __init__(self, text: str = ""):
60 if "\n" in text or "\r" in text:
61 self._lines = text.splitlines()
62 elif text and len(text) % 80 == 0:
63 # Astropy WCS.to_header_string() yields a single concatenated
64 # FITS header block; FitsChan expects one card per source line.
65 self._lines = [text[n : n + 80] for n in range(0, len(text), 80)]
66 else:
67 self._lines = text.splitlines()
69 def astsource(self) -> str | None:
70 if not self._lines:
71 return None
72 return self._lines.pop(0)
74 def astsink(self, line: str) -> None:
75 self._lines.append(line)
77 def to_string(self) -> str:
78 if not self._lines:
79 return ""
80 return "\n".join(self._lines) + "\n"
82 class _AstWrapper:
83 def __init__(self, obj: Any):
84 self._obj = obj
86 @classmethod
87 def _from_obj(cls, obj: Any) -> Any:
88 self = cls.__new__(cls)
89 _AstWrapper.__init__(self, obj)
90 return self
92 def __getattr__(self, name: str) -> Any:
93 return _wrap_ast_object(getattr(self._obj, name))
95 def _serialize_ast_object(obj: Any) -> str:
96 sink = _StringStream()
97 chan = StarAst.Channel(None, sink)
98 chan.write(_unwrap_ast_object(obj))
99 return sink.to_string()
101 def _deserialize_ast_object(serialized: str) -> Any:
102 source = _StringStream(serialized)
103 chan = StarAst.Channel(source)
104 return chan.read()
106 def _coerce_to_star_ast_object(obj: Any) -> Any:
107 match obj:
108 case _AstWrapper():
109 return obj._obj
110 case StarAst.FrameSet() | StarAst.Mapping() | StarAst.Frame():
111 return obj
112 case _:
113 if hasattr(obj, "show"):
114 try:
115 return _deserialize_ast_object(obj.show())
116 except Exception:
117 return obj
118 return obj
120 def _unwrap_ast_object(obj: Any) -> Any:
121 return _coerce_to_star_ast_object(obj)
123 def _wrap_ast_object(obj: Any) -> Any:
124 match obj:
125 case None:
126 return obj
127 case _AstWrapper():
128 return obj
129 case StarAst.FrameSet():
130 return _FrameSet._from_obj(obj)
131 case StarAst.SkyFrame():
132 return _SkyFrame._from_obj(obj)
133 case StarAst.Frame():
134 return _Frame._from_obj(obj)
135 case StarAst.Mapping():
136 return _Mapping._from_obj(obj)
137 case _:
138 coerced = _coerce_to_star_ast_object(obj)
139 if coerced is not obj:
140 return _wrap_ast_object(coerced)
141 return obj
143 class _Mapping(_AstWrapper):
144 @staticmethod
145 def fromString(serialized: str) -> _Mapping:
146 obj = _deserialize_ast_object(serialized)
147 if not isinstance(obj, StarAst.Mapping):
148 raise TypeError(f"Serialized object is not a Mapping: {type(obj)}")
149 return _Mapping._from_obj(obj)
151 def show(self) -> str:
152 return _serialize_ast_object(self._obj)
154 def simplified(self) -> _Mapping:
155 return _Mapping._from_obj(self._obj.simplify())
157 def applyForward(self, xy: Any) -> Any:
158 return self._obj.tran(xy, True)
160 def applyInverse(self, xy: Any) -> Any:
161 return self._obj.tran(xy, False)
163 def then(self, other: _Mapping) -> _Mapping:
164 return _Mapping._from_obj(StarAst.CmpMap(self._obj, _unwrap_ast_object(other), True))
166 def inverted(self) -> _Mapping:
167 copy = self._obj.copy()
168 copy.invert()
169 return _Mapping._from_obj(copy)
171 class _UnitMap(_Mapping):
172 def __init__(self, n_coord: int):
173 super().__init__(StarAst.UnitMap(n_coord))
175 class _ShiftMap(_Mapping):
176 def __init__(self, shift: Iterable[float]):
177 super().__init__(StarAst.ShiftMap(list(shift)))
179 class _Frame(_AstWrapper):
180 def __init__(self, n_axes: int, options: str = ""):
181 super().__init__(StarAst.Frame(n_axes, options))
183 @property
184 def ident(self) -> str:
185 return self._obj.Ident
187 def setUnit(self, axis: int, unit: str) -> None:
188 setattr(self._obj, f"Unit_{axis}", unit)
190 def getUnit(self, axis: int) -> str:
191 return getattr(self._obj, f"Unit_{axis}")
193 def setLabel(self, axis: int, label: str) -> None:
194 setattr(self._obj, f"Label_{axis}", label)
196 def getBottom(self, axis: int) -> float:
197 return getattr(self._obj, f"Bottom_{axis}")
199 def getTop(self, axis: int) -> float:
200 return getattr(self._obj, f"Top_{axis}")
202 def show(self) -> str:
203 return _serialize_ast_object(self._obj)
205 class _SkyFrame(_Frame):
206 def __init__(self, options: str = ""):
207 _AstWrapper.__init__(self, StarAst.SkyFrame(options))
209 class _FrameSet(_AstWrapper):
210 BASE = 1
212 def __init__(self, base_frame: _Frame):
213 super().__init__(StarAst.FrameSet(_unwrap_ast_object(base_frame)))
215 @property
216 def nFrame(self) -> int:
217 return self._obj.Nframe
219 @property
220 def base(self) -> int:
221 return self._obj.Base
223 @base.setter
224 def base(self, value: int) -> None:
225 self._obj.Base = value
227 @property
228 def current(self) -> int:
229 return self._obj.Current
231 @current.setter
232 def current(self, value: int) -> None:
233 self._obj.Current = value
235 def addFrame(self, iframe: int, mapping: _Mapping, frame: _Frame) -> None:
236 self._obj.addframe(iframe, _unwrap_ast_object(mapping), _unwrap_ast_object(frame))
238 def getFrame(self, iframe: int, copy: bool = True) -> _Frame:
239 del copy
240 return _wrap_ast_object(self._obj.getframe(iframe))
242 def getMapping(self, iframe1: int | None = None, iframe2: int | None = None) -> _Mapping:
243 if iframe1 is None:
244 iframe1 = self.base
245 if iframe2 is None:
246 iframe2 = self.current
247 return _wrap_ast_object(self._obj.getmapping(iframe1, iframe2))
249 def show(self) -> str:
250 return _serialize_ast_object(self._obj)
252 @staticmethod
253 def fromString(serialized: str) -> _FrameSet:
254 obj = _deserialize_ast_object(serialized)
255 if not isinstance(obj, StarAst.FrameSet):
256 raise TypeError(f"Serialized object is not a FrameSet: {type(obj)}")
257 return _FrameSet._from_obj(obj)
259 class _FitsChan:
260 def __init__(self, stream: _StringStream | None = None, options: str = ""):
261 source = stream if stream is not None else None
262 sink = stream if stream is not None else None
263 self._obj = StarAst.FitsChan(source, sink, options)
265 def read(self) -> Any:
266 return _wrap_ast_object(self._obj.read())
268 def write(self, obj: Any) -> int:
269 return self._obj.write(_unwrap_ast_object(obj))
271 def setFitsI(self, keyword: str, value: int) -> None:
272 self._obj.setfitsI(keyword, value, "", 1)
274 def __iter__(self) -> Any:
275 return iter(self._obj)
277 StringStream = _StringStream
278 FitsChan = _FitsChan
279 Mapping = _Mapping
280 UnitMap = _UnitMap
281 ShiftMap = _ShiftMap
282 Frame = _Frame
283 SkyFrame = _SkyFrame
284 FrameSet = _FrameSet
286 def wrap_mapping(mapping: Any) -> Any:
287 return _wrap_ast_object(mapping)
289 def wrap_frame_set(frame_set: Any) -> Any:
290 return _wrap_ast_object(frame_set)
292else:
293 assert AstShim is not None
295 StringStream = AstShim.StringStream
296 FitsChan = AstShim.FitsChan
297 Mapping = AstShim.Mapping
298 UnitMap = AstShim.UnitMap
299 ShiftMap = AstShim.ShiftMap
300 Frame = AstShim.Frame
301 SkyFrame = AstShim.SkyFrame
302 FrameSet = AstShim.FrameSet
304 def wrap_mapping(mapping: Any) -> Any:
305 return mapping
307 def wrap_frame_set(frame_set: Any) -> Any:
308 return frame_set