Coverage for python/lsst/obs/base/utils.py: 21%

65 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-12 01:53 -0800

1# This file is part of obs_base. 

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# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22__all__ = ('InitialSkyWcsError', 'createInitialSkyWcs', 'createInitialSkyWcsFromBoresight', 'bboxFromIraf') 

23 

24import re 

25import lsst.geom as geom 

26 

27from . import Instrument 

28from lsst.afw.cameraGeom import PIXELS, FIELD_ANGLE 

29from lsst.afw.image import RotType 

30from lsst.afw.geom.skyWcs import makeSkyWcs 

31import lsst.pex.exceptions 

32from lsst.utils import doImport 

33 

34 

35class InitialSkyWcsError(Exception): 

36 """For handling failures when creating a SkyWcs from a camera geometry and 

37 boresight. 

38 

39 Typically used as a chained exception from a lower level exception. 

40 """ 

41 pass 

42 

43 

44def createInitialSkyWcs(visitInfo, detector, flipX=False): 

45 """Create a SkyWcs from the visit information and detector geometry. 

46 

47 A typical usecase for this is to create the initial WCS for a newly-read 

48 raw exposure. 

49 

50 

51 Parameters 

52 ---------- 

53 visitInfo : `lsst.afw.image.VisitInfo` 

54 Where to get the telescope boresight and rotator angle from. 

55 detector : `lsst.afw.cameraGeom.Detector` 

56 Where to get the camera geomtry from. 

57 flipX : `bool`, optional 

58 If False, +X is along W, if True +X is along E. 

59 

60 Returns 

61 ------- 

62 skyWcs : `lsst.afw.geom.SkyWcs` 

63 The new composed WCS. 

64 

65 Raises 

66 ------ 

67 InitialSkyWcsError 

68 Raised if there is an error generating the SkyWcs, chained from the 

69 lower-level exception if available. 

70 """ 

71 if visitInfo.getRotType() != RotType.SKY: 

72 msg = (f"Cannot create SkyWcs from camera geometry: rotator angle defined using " 

73 f"RotType={visitInfo.getRotType()} instead of SKY.") 

74 raise InitialSkyWcsError(msg) 

75 orientation = visitInfo.getBoresightRotAngle() 

76 boresight = visitInfo.getBoresightRaDec() 

77 return createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX) 

78 

79 

80def createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=False): 

81 """Create a SkyWcs from the telescope boresight and detector geometry. 

82 

83 A typical usecase for this is to create the initial WCS for a newly-read 

84 raw exposure. 

85 

86 Parameters 

87 ---------- 

88 boresight : `lsst.geom.SpherePoint` 

89 The ICRS boresight RA/Dec 

90 orientation : `lsst.geom.Angle` 

91 The rotation angle of the focal plane on the sky. 

92 detector : `lsst.afw.cameraGeom.Detector` 

93 Where to get the camera geomtry from. 

94 flipX : `bool`, optional 

95 If False, +X is along W, if True +X is along E. 

96 

97 Returns 

98 ------- 

99 skyWcs : `lsst.afw.geom.SkyWcs` 

100 The new composed WCS. 

101 

102 Raises 

103 ------ 

104 InitialSkyWcsError 

105 Raised if there is an error generating the SkyWcs, chained from the 

106 lower-level exception if available. 

107 """ 

108 try: 

109 pixelsToFieldAngle = detector.getTransform(detector.makeCameraSys(PIXELS), 

110 detector.makeCameraSys(FIELD_ANGLE)) 

111 except lsst.pex.exceptions.InvalidParameterError as e: 

112 raise InitialSkyWcsError("Cannot compute PIXELS to FIELD_ANGLE Transform.") from e 

113 return makeSkyWcs(pixelsToFieldAngle, orientation, flipX, boresight) 

114 

115 

116def bboxFromIraf(irafBBoxStr): 

117 """Return a Box2I corresponding to an IRAF-style BBOX 

118 

119 [x0:x1,y0:y1] where x0 and x1 are the one-indexed start and end columns, 

120 and correspondingly y0 and y1 are the start and end rows. 

121 """ 

122 

123 mat = re.search(r"^\[([-\d]+):([-\d]+),([-\d]+):([-\d]+)\]$", irafBBoxStr) 

124 if not mat: 

125 raise RuntimeError("Unable to parse IRAF-style bbox \"%s\"" % irafBBoxStr) 

126 x0, x1, y0, y1 = [int(_) for _ in mat.groups()] 

127 

128 return geom.BoxI(geom.PointI(x0 - 1, y0 - 1), geom.PointI(x1 - 1, y1 - 1)) 

129 

130 

131def getInstrument(instrumentName, registry=None, collection_prefix=None): 

132 """Return an instance of a named instrument. 

133 

134 If the instrument name not is qualified (does not contain a '.') and a 

135 butler registry is provided, this will attempt to load the instrument using 

136 Instrument.fromName. Otherwise the instrument will be imported and 

137 instantiated. 

138 

139 Parameters 

140 ---------- 

141 instrumentName : string 

142 The name or fully-qualified class name of an instrument. 

143 registry : `lsst.daf.butler.Registry`, optional 

144 Butler registry to query to find information about the instrument, by 

145 default None 

146 collection_prefix : `str`, optional 

147 Prefix for collection names to use instead of the intrument's own name. 

148 This is primarily for use in simulated-data repositories, where the 

149 instrument name may not be necessary and/or sufficient to distinguish 

150 between collections. 

151 

152 Returns 

153 ------- 

154 Instrument subclass instance 

155 The instantiated instrument. 

156 

157 Raises 

158 ------ 

159 RuntimeError 

160 If the instrument can not be imported, instantiated, or obtained from 

161 the registry. 

162 TypeError 

163 If the instrument is not a subclass of lsst.obs.base.Instrument. 

164 """ 

165 if "." not in instrumentName and registry is not None: 

166 try: 

167 instr = Instrument.fromName(instrumentName, registry, collection_prefix=collection_prefix) 

168 except Exception as err: 

169 raise RuntimeError( 

170 f"Could not get instrument from name: {instrumentName}. Failed with exception: {err}") 

171 else: 

172 try: 

173 instr = doImport(instrumentName) 

174 except Exception as err: 

175 raise RuntimeError(f"Could not import instrument: {instrumentName}. Failed with exception: {err}") 

176 instr = instr(collection_prefix=collection_prefix) 

177 if not isinstance(instr, Instrument): 

178 raise TypeError(f"{instrumentName} is not an Instrument subclass.") 

179 return instr 

180 

181 

182# TODO remove the impl in pipe_base? (NB this combines setDottedAtr AND the 

183# handling in ConfigValueAction.__call__) 

184def setDottedAttr(item, name, value): 

185 """Set an instance attribute (like `setattr` but accepting hierarchical 

186 names such as ``foo.bar.baz``) If the attribute can not be set as a string, 

187 will attempt to set the attribute with the result of eval'ing the value. 

188 

189 Parameters 

190 ---------- 

191 item : obj 

192 Object whose attribute is to be set. 

193 name : `str` 

194 Name of attribute to set. 

195 value : obj 

196 New value for the attribute. 

197 

198 Notes 

199 ----- 

200 For example if name is ``foo.bar.baz`` then ``item.foo.bar.baz`` 

201 is set to the specified value. 

202 

203 Raises 

204 ------ 

205 AttributeError 

206 If the item does not have a field specified by name that can be set. 

207 RuntimeError 

208 If the value can not be set as a string or rendered by eval, or if 

209 there is an error setting the attribute with the rendered value. 

210 """ 

211 subitem = item 

212 subnameList = name.split(".") 

213 for subname in subnameList[:-1]: 

214 subitem = getattr(subitem, subname) 

215 try: 

216 setattr(subitem, subnameList[-1], value) 

217 except AttributeError: 

218 raise AttributeError(f"No field: {name!r}") 

219 except Exception: 

220 try: 

221 v = eval(value, {}) 

222 except Exception: 

223 raise RuntimeError(f"Cannot render {value!r} as a value for {name!r}") 

224 try: 

225 setattr(subitem, subnameList[-1], v) 

226 except Exception as e: 

227 raise RuntimeError(f"Cannot set config. {name}={value!r}: {e}") 

228 

229 

230def setDottedAttrs(item, attrs): 

231 for name, value in attrs: 

232 setDottedAttr(item, name, value)