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

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

65 statements  

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 

25 

26import lsst.geom as geom 

27import lsst.pex.exceptions 

28from lsst.afw.cameraGeom import FIELD_ANGLE, PIXELS 

29from lsst.afw.geom.skyWcs import makeSkyWcs 

30from lsst.afw.image import RotType 

31from lsst.utils import doImport 

32 

33from ._instrument import Instrument 

34 

35 

36class InitialSkyWcsError(Exception): 

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

38 boresight. 

39 

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

41 """ 

42 

43 pass 

44 

45 

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

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

48 

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

50 raw exposure. 

51 

52 

53 Parameters 

54 ---------- 

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

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

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

58 Where to get the camera geomtry from. 

59 flipX : `bool`, optional 

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

61 

62 Returns 

63 ------- 

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

65 The new composed WCS. 

66 

67 Raises 

68 ------ 

69 InitialSkyWcsError 

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

71 lower-level exception if available. 

72 """ 

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

74 msg = ( 

75 f"Cannot create SkyWcs from camera geometry: rotator angle defined using " 

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

77 ) 

78 raise InitialSkyWcsError(msg) 

79 orientation = visitInfo.getBoresightRotAngle() 

80 boresight = visitInfo.getBoresightRaDec() 

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

82 

83 

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

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

86 

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

88 raw exposure. 

89 

90 Parameters 

91 ---------- 

92 boresight : `lsst.geom.SpherePoint` 

93 The ICRS boresight RA/Dec 

94 orientation : `lsst.geom.Angle` 

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

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

97 Where to get the camera geomtry from. 

98 flipX : `bool`, optional 

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

100 

101 Returns 

102 ------- 

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

104 The new composed WCS. 

105 

106 Raises 

107 ------ 

108 InitialSkyWcsError 

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

110 lower-level exception if available. 

111 """ 

112 try: 

113 pixelsToFieldAngle = detector.getTransform( 

114 detector.makeCameraSys(PIXELS), detector.makeCameraSys(FIELD_ANGLE) 

115 ) 

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

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

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

119 

120 

121def bboxFromIraf(irafBBoxStr): 

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

123 

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

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

126 """ 

127 

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

129 if not mat: 

130 raise RuntimeError('Unable to parse IRAF-style bbox "%s"' % irafBBoxStr) 

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

132 

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

134 

135 

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

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

138 

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

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

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

142 instantiated. 

143 

144 Parameters 

145 ---------- 

146 instrumentName : string 

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

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

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

150 default None 

151 collection_prefix : `str`, optional 

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

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

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

155 between collections. 

156 

157 Returns 

158 ------- 

159 Instrument subclass instance 

160 The instantiated instrument. 

161 

162 Raises 

163 ------ 

164 RuntimeError 

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

166 the registry. 

167 TypeError 

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

169 """ 

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

171 try: 

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

173 except Exception as err: 

174 raise RuntimeError( 

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

176 ) 

177 else: 

178 try: 

179 instr = doImport(instrumentName) 

180 except Exception as err: 

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

182 instr = instr(collection_prefix=collection_prefix) 

183 if not isinstance(instr, Instrument): 

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

185 return instr 

186 

187 

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

189# handling in ConfigValueAction.__call__) 

190def setDottedAttr(item, name, value): 

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

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

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

194 

195 Parameters 

196 ---------- 

197 item : obj 

198 Object whose attribute is to be set. 

199 name : `str` 

200 Name of attribute to set. 

201 value : obj 

202 New value for the attribute. 

203 

204 Notes 

205 ----- 

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

207 is set to the specified value. 

208 

209 Raises 

210 ------ 

211 AttributeError 

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

213 RuntimeError 

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

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

216 """ 

217 subitem = item 

218 subnameList = name.split(".") 

219 for subname in subnameList[:-1]: 

220 subitem = getattr(subitem, subname) 

221 try: 

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

223 except AttributeError: 

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

225 except Exception: 

226 try: 

227 v = eval(value, {}) 

228 except Exception: 

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

230 try: 

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

232 except Exception as e: 

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

234 

235 

236def setDottedAttrs(item, attrs): 

237 for name, value in attrs: 

238 setDottedAttr(item, name, value)