Coverage for python / lsst / images / _transforms / _ast.py: 5%

150 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-07 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. 

11 

12from __future__ import annotations 

13 

14from collections.abc import Iterable 

15from typing import TYPE_CHECKING, Any, ClassVar, Self 

16 

17__all__ = ( 

18 "USING_STARLINK_PYAST", 

19 "FitsChan", 

20 "Frame", 

21 "FrameSet", 

22 "Mapping", 

23 "ShiftMap", 

24 "SkyFrame", 

25 "StringStream", 

26 "UnitMap", 

27) 

28 

29if TYPE_CHECKING: 

30 import starlink.Ast 

31 

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 

49 

50 USING_STARLINK_PYAST = True 

51 else: 

52 USING_STARLINK_PYAST = False 

53 

54 

55if USING_STARLINK_PYAST: 55 ↛ 57line 55 didn't jump to line 57 because the condition on line 55 was never true

56 

57 class StringStream: 

58 """A bridge object that mimics both astshim.StringStream and the 

59 interface expected by starlink.Ast.Channel. 

60 

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 """ 

68 

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() 

78 

79 def astsource(self) -> str | None: 

80 if not self._lines: 

81 return None 

82 return self._lines.pop(0) 

83 

84 def astsink(self, line: str) -> None: 

85 self._lines.append(line) 

86 

87 def to_string(self) -> str: 

88 if not self._lines: 

89 return "" 

90 return "\n".join(self._lines) + "\n" 

91 

92 class Object: 

93 """Bridge class that exposes the `astshim.Object` interface while 

94 being backed by an `astshim.Ast.Object`. 

95 """ 

96 

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 

101 

102 _IMPL_TYPE: ClassVar[type[starlink.Ast.Object]] = starlink.Ast.Object 

103 

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() 

110 

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()) 

116 

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 

123 

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 

130 

131 class Mapping(Object): 

132 _IMPL_TYPE: ClassVar[type[starlink.Ast.Mapping]] = starlink.Ast.Mapping 

133 

134 def simplified(self) -> Mapping: 

135 return Mapping._wrap(self._impl.simplify()) 

136 

137 def applyForward(self, xy: Any) -> Any: 

138 return self._impl.tran(xy, True) 

139 

140 def applyInverse(self, xy: Any) -> Any: 

141 return self._impl.tran(xy, False) 

142 

143 def then(self, other: Mapping) -> Mapping: 

144 return Mapping._wrap(starlink.Ast.CmpMap(self._impl, other._impl, True)) 

145 

146 def inverted(self) -> Mapping: 

147 copy = self._impl.copy() 

148 copy.invert() 

149 return Mapping._wrap(copy) 

150 

151 class UnitMap(Mapping): 

152 def __init__(self, n_coord: int): 

153 super().__init__(starlink.Ast.UnitMap(n_coord)) 

154 

155 _IMPL_TYPE: ClassVar[type[starlink.Ast.UnitMap]] = starlink.Ast.UnitMap 

156 

157 class ShiftMap(Mapping): 

158 def __init__(self, shift: Iterable[float]): 

159 super().__init__(starlink.Ast.ShiftMap(list(shift))) 

160 

161 _IMPL_TYPE: ClassVar[type[starlink.Ast.ShiftMap]] = starlink.Ast.ShiftMap 

162 

163 class Frame(Mapping): 

164 def __init__(self, n_axes: int, options: str = ""): 

165 super().__init__(starlink.Ast.Frame(n_axes, options)) 

166 

167 _IMPL_TYPE: ClassVar[type[starlink.Ast.Frame]] = starlink.Ast.Frame 

168 

169 @property 

170 def ident(self) -> str: 

171 return self._impl.Ident 

172 

173 def setUnit(self, axis: int, unit: str) -> None: 

174 setattr(self._impl, f"Unit_{axis}", unit) 

175 

176 def getUnit(self, axis: int) -> str: 

177 return getattr(self._impl, f"Unit_{axis}") 

178 

179 def setLabel(self, axis: int, label: str) -> None: 

180 setattr(self._impl, f"Label_{axis}", label) 

181 

182 def getBottom(self, axis: int) -> float: 

183 return getattr(self._impl, f"Bottom_{axis}") 

184 

185 def getTop(self, axis: int) -> float: 

186 return getattr(self._impl, f"Top_{axis}") 

187 

188 class SkyFrame(Frame): 

189 def __init__(self, options: str = ""): 

190 Object.__init__(self, starlink.Ast.SkyFrame(options)) 

191 

192 _IMPL_TYPE: ClassVar[type[starlink.Ast.SkyFrame]] = starlink.Ast.SkyFrame 

193 

194 class FrameSet(Frame): 

195 def __init__(self, base_frame: Frame): 

196 Object.__init__(self, starlink.Ast.FrameSet(base_frame._impl)) 

197 

198 BASE: ClassVar[int] = 1 

199 _IMPL_TYPE: ClassVar[type[starlink.Ast.FrameSet]] = starlink.Ast.FrameSet 

200 

201 @property 

202 def nFrame(self) -> int: 

203 return self._impl.Nframe 

204 

205 @property 

206 def base(self) -> int: 

207 return self._impl.Base 

208 

209 @base.setter 

210 def base(self, value: int) -> None: 

211 self._impl.Base = value 

212 

213 @property 

214 def current(self) -> int: 

215 return self._impl.Current 

216 

217 @current.setter 

218 def current(self, value: int) -> None: 

219 self._impl.Current = value 

220 

221 def addFrame(self, iframe: int, mapping: Mapping, frame: Frame) -> None: 

222 self._impl.addframe(iframe, mapping._impl, frame._impl) 

223 

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) 

229 

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)) 

236 

237 class FrameDict(FrameSet): 

238 def __init__(self, obj: Object): 

239 Object.__init__(self, obj._impl) 

240 

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)) 

246 

247 _IMPL_TYPE: ClassVar[type[starlink.Ast.FitsChan]] = starlink.Ast.FitsChan 

248 

249 def read(self) -> Any: 

250 return Object._wrap(self._impl.read()) 

251 

252 def write(self, obj: Any) -> int: 

253 return self._impl.write(obj._impl) 

254 

255 def setFitsI(self, keyword: str, value: int) -> None: 

256 self._impl.setfitsI(keyword, value, "", 1) 

257 

258 def __iter__(self) -> Any: 

259 return iter(self._impl)