Coverage for python / lsst / images / _transforms / _ast.py: 5%
150 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-28 09:01 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-28 09:01 +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
14from collections.abc import Iterable
15from typing import TYPE_CHECKING, Any, ClassVar, Self
17__all__ = (
18 "USING_STARLINK_PYAST",
19 "FitsChan",
20 "Frame",
21 "FrameSet",
22 "Mapping",
23 "ShiftMap",
24 "SkyFrame",
25 "StringStream",
26 "UnitMap",
27)
29if TYPE_CHECKING:
30 import starlink.Ast
32 USING_STARLINK_PYAST = True
33else:
34 try:
35 from astshim import (
36 FitsChan,
37 Frame,
38 FrameDict,
39 FrameSet,
40 Mapping,
41 Object,
42 ShiftMap,
43 SkyFrame,
44 StringStream,
45 UnitMap,
46 )
47 except ImportError:
48 import starlink.Ast
50 USING_STARLINK_PYAST = True
51 else:
52 USING_STARLINK_PYAST = False
55if USING_STARLINK_PYAST: 55 ↛ 57line 55 didn't jump to line 57 because the condition on line 55 was never true
57 class StringStream:
58 """A bridge object that mimics both astshim.StringStream and the
59 interface expected by starlink.Ast.Channel.
61 Notes
62 -----
63 This object can be constructed like an `astshim.StringStream`, but its
64 `astsink` and `astsource` methods actually correspond to the
65 `starlink.Ast` interface, so we can use `starlink.Ast` objects to
66 implement the `FitsChan` classes in this module
67 """
69 def __init__(self, text: str = ""):
70 if "\n" in text or "\r" in text:
71 self._lines = text.splitlines()
72 elif text and len(text) % 80 == 0:
73 # Astropy WCS.to_header_string() yields a single concatenated
74 # FITS header block; FitsChan expects one card per source line.
75 self._lines = [text[n : n + 80] for n in range(0, len(text), 80)]
76 else:
77 self._lines = text.splitlines()
79 def astsource(self) -> str | None:
80 if not self._lines:
81 return None
82 return self._lines.pop(0)
84 def astsink(self, line: str) -> None:
85 self._lines.append(line)
87 def to_string(self) -> str:
88 if not self._lines:
89 return ""
90 return "\n".join(self._lines) + "\n"
92 class Object:
93 """Bridge class that exposes the `astshim.Object` interface while
94 being backed by an `astshim.Ast.Object`.
95 """
97 def __init__(self, impl: starlink.Ast.Object):
98 if not isinstance(impl, self._IMPL_TYPE):
99 raise TypeError(f"{type(self).__name__} cannot wrap {type(impl).__name__}.")
100 self._impl = impl
102 _IMPL_TYPE: ClassVar[type[starlink.Ast.Object]] = starlink.Ast.Object
104 def show(self, showComments: bool = True) -> str:
105 sink = StringStream()
106 options = "" if showComments else "Comment=0"
107 chan = starlink.Ast.Channel(None, sink, options=options)
108 chan.write(self._impl)
109 return sink.to_string()
111 @classmethod
112 def fromString(cls, serialized: str) -> Self:
113 source = StringStream(serialized)
114 channel = starlink.Ast.Channel(source)
115 return cls._wrap(channel.read())
117 @classmethod
118 def _wrap(cls, impl: starlink.Ast.Object) -> Self:
119 subcls = cls._most_derived_type(impl)
120 result = object.__new__(subcls)
121 Object.__init__(result, impl)
122 return result
124 @classmethod
125 def _most_derived_type(cls, impl: starlink.Ast.Object) -> type[Self]:
126 for subcls in cls.__subclasses__():
127 if isinstance(impl, subcls._IMPL_TYPE):
128 return subcls._most_derived_type(impl)
129 return cls
131 class Mapping(Object):
132 _IMPL_TYPE: ClassVar[type[starlink.Ast.Mapping]] = starlink.Ast.Mapping
134 def simplified(self) -> Mapping:
135 return Mapping._wrap(self._impl.simplify())
137 def applyForward(self, xy: Any) -> Any:
138 return self._impl.tran(xy, True)
140 def applyInverse(self, xy: Any) -> Any:
141 return self._impl.tran(xy, False)
143 def then(self, other: Mapping) -> Mapping:
144 return Mapping._wrap(starlink.Ast.CmpMap(self._impl, other._impl, True))
146 def inverted(self) -> Mapping:
147 copy = self._impl.copy()
148 copy.invert()
149 return Mapping._wrap(copy)
151 class UnitMap(Mapping):
152 def __init__(self, n_coord: int):
153 super().__init__(starlink.Ast.UnitMap(n_coord))
155 _IMPL_TYPE: ClassVar[type[starlink.Ast.UnitMap]] = starlink.Ast.UnitMap
157 class ShiftMap(Mapping):
158 def __init__(self, shift: Iterable[float]):
159 super().__init__(starlink.Ast.ShiftMap(list(shift)))
161 _IMPL_TYPE: ClassVar[type[starlink.Ast.ShiftMap]] = starlink.Ast.ShiftMap
163 class Frame(Mapping):
164 def __init__(self, n_axes: int, options: str = ""):
165 super().__init__(starlink.Ast.Frame(n_axes, options))
167 _IMPL_TYPE: ClassVar[type[starlink.Ast.Frame]] = starlink.Ast.Frame
169 @property
170 def ident(self) -> str:
171 return self._impl.Ident
173 def setUnit(self, axis: int, unit: str) -> None:
174 setattr(self._impl, f"Unit_{axis}", unit)
176 def getUnit(self, axis: int) -> str:
177 return getattr(self._impl, f"Unit_{axis}")
179 def setLabel(self, axis: int, label: str) -> None:
180 setattr(self._impl, f"Label_{axis}", label)
182 def getBottom(self, axis: int) -> float:
183 return getattr(self._impl, f"Bottom_{axis}")
185 def getTop(self, axis: int) -> float:
186 return getattr(self._impl, f"Top_{axis}")
188 class SkyFrame(Frame):
189 def __init__(self, options: str = ""):
190 Object.__init__(self, starlink.Ast.SkyFrame(options))
192 _IMPL_TYPE: ClassVar[type[starlink.Ast.SkyFrame]] = starlink.Ast.SkyFrame
194 class FrameSet(Frame):
195 def __init__(self, base_frame: Frame):
196 Object.__init__(self, starlink.Ast.FrameSet(base_frame._impl))
198 BASE: ClassVar[int] = 1
199 _IMPL_TYPE: ClassVar[type[starlink.Ast.FrameSet]] = starlink.Ast.FrameSet
201 @property
202 def nFrame(self) -> int:
203 return self._impl.Nframe
205 @property
206 def base(self) -> int:
207 return self._impl.Base
209 @base.setter
210 def base(self, value: int) -> None:
211 self._impl.Base = value
213 @property
214 def current(self) -> int:
215 return self._impl.Current
217 @current.setter
218 def current(self, value: int) -> None:
219 self._impl.Current = value
221 def addFrame(self, iframe: int, mapping: Mapping, frame: Frame) -> None:
222 self._impl.addframe(iframe, mapping._impl, frame._impl)
224 def getFrame(self, iframe: int, copy: bool = True) -> Frame:
225 result = self._impl.getframe(iframe)
226 if copy:
227 result = result.copy()
228 return Frame._wrap(result)
230 def getMapping(self, iframe1: int | None = None, iframe2: int | None = None) -> Mapping:
231 if iframe1 is None:
232 iframe1 = self.base
233 if iframe2 is None:
234 iframe2 = self.current
235 return Mapping._wrap(self._impl.getmapping(iframe1, iframe2))
237 class FrameDict(FrameSet):
238 def __init__(self, obj: Object):
239 Object.__init__(self, obj._impl)
241 class FitsChan(Object):
242 def __init__(self, stream: StringStream | None = None, options: str = ""):
243 source = stream if stream is not None else None
244 sink = stream if stream is not None else None
245 super().__init__(starlink.Ast.FitsChan(source, sink, options))
247 _IMPL_TYPE: ClassVar[type[starlink.Ast.FitsChan]] = starlink.Ast.FitsChan
249 def read(self) -> Any:
250 return Object._wrap(self._impl.read())
252 def write(self, obj: Any) -> int:
253 return self._impl.write(obj._impl)
255 def setFitsI(self, keyword: str, value: int) -> None:
256 self._impl.setfitsI(keyword, value, "", 1)
258 def __iter__(self) -> Any:
259 return iter(self._impl)