Coverage for python/lsst/cbp/testUtils.py: 20%

84 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-01 01:22 -0700

1# This file is part of cbp. 

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"""SampleCoordinateConverter class: make a CoordinateConverter for tests""" 

22 

23__all__ = ["SampleCoordinateConverter"] 

24 

25import numpy as np 

26 

27import lsst.geom 

28from lsst.afw.geom import makeRadialTransform 

29from lsst.afw.cameraGeom import (Camera, Amplifier, FIELD_ANGLE, ReadoutCorner, 

30 addDetectorBuilderFromConfig, DetectorConfig) 

31from .coordinateConverterConfig import CoordinateConverterConfig 

32from .coordinateConverter import CoordinateConverter 

33from .computeHolePositions import computeHolePositions 

34from .maskInfo import MaskInfo 

35 

36 

37class SampleCoordinateConverter: 

38 """An object containing a CoordinateConverter and the information 

39 used to create it. 

40 

41 Parameters 

42 ---------- 

43 detectorFracPosList : `iterable` of pair of `float` (optional) 

44 Position of the center of each detector, as a fraction of 

45 the width and height of the detector. 

46 The first element must have value (0, 0). 

47 See the field of the same name for more information. 

48 Defaults to:: 

49 

50 ( 

51 (0, 0), 

52 (1.01, 0), # 1.01: leave a 1% gap 

53 (-4, 7), # a corner detector in the LSST camera 

54 ) 

55 

56 holeFracPosList : `iterable` of pair of `float` (optional) 

57 Positions of holes on a given detector, 

58 as a fraction of the distance from lower left corner 

59 to upper right corner. Thus (0.5, 0.5) is centered 

60 on the detector. 

61 Defaults to `((0, 0), (0.75, 0.75))`. 

62 

63 Notes 

64 ----- 

65 **Attributes** 

66 

67 detectorWidthPix : `int` 

68 Width of each detector, in pixels. 

69 detectorHeightPix : `int` 

70 Height of each detector, in pixels. 

71 pixelSizeMm : `float` 

72 Width = height of each pixel, in mm. 

73 plateScale : `lsst.geom.Angle` 

74 Plate scale: in angle on the sky per mm on the focal plane. 

75 detectorFracPosList : `iterable` of pair of `float` 

76 Position of the center of each detector, as a fraction of 

77 the width and height of the detector. For instance 

78 (0, 0) is a detector centered on the focal plane 

79 and (1, 0) is adjacent to a centered detector, 

80 in the direction of increasing focal plane x. 

81 holeFracPosList : `iterable` of pair of `float` 

82 Positions of holes on a given detector, 

83 as a fraction of the distance from lower left corner 

84 to upper right corner. Thus (0.5, 0.5) is centered 

85 on the detector. 

86 cameraGeom : `lsst.afw.cameraGeom.Camera` 

87 Camera geometry. There will be one detector per entry in 

88 detectorFracPosList with names "D0", "D1", ... 

89 Detector "D0" is centered on the focal plane. 

90 config : `lsst.cbp.CoordinateConverterConfig` 

91 Basic configuration for ``coordinateConverter``. 

92 maskInfo : `lsst.cbp.MaskInfo` 

93 CBP mask information. 

94 coordinateConverter : `lsst.cbp.CoordinateConverter` 

95 The test coordinate converter. 

96 """ 

97 

98 def __init__(self, detectorFracPosList=None, holeFracPosList=None, 

99 telFlipX=False, cbpFlipX=False): 

100 # these value are close to LSST and are rectangular 

101 # in order to catch axis transposition errors 

102 self.detectorWidthPix = 4000 

103 self.detectorHeightPix = 4095 

104 self.pixelSizeMm = 0.01 

105 self.plateScale = 20 * lsst.geom.arcseconds 

106 if holeFracPosList is None: 

107 holeFracPosList = ((0.5, 0.5), (0.75, 0.75)) 

108 self.holeFracPosList = holeFracPosList 

109 if detectorFracPosList is None: 

110 detectorFracPosList = ( 

111 (0, 0), 

112 (1.01, 0), # 1.01: leave a 1% gap 

113 (-4.04, 7.07), # approximately a corner detector in the LSST camera 

114 ) 

115 self.detectorFracPosList = detectorFracPosList 

116 

117 self.cameraGeom = self.makeCameraGeom() 

118 self.config = self.makeCoordinateConverterConfig( 

119 telFlipX=telFlipX, 

120 cbpFlipX=cbpFlipX, 

121 ) 

122 self.maskInfo = self.makeMaskInfo() 

123 self.coordinateConverter = CoordinateConverter( 

124 config=self.config, 

125 maskInfo=self.maskInfo, 

126 cameraGeom=self.cameraGeom, 

127 ) 

128 

129 def makeCoordinateConverterConfig(self, telFlipX, cbpFlipX): 

130 """Make a coordinate converter config. 

131 

132 Parameters 

133 ---------- 

134 telFlipX : `bool` 

135 :ref:`Flip <lsst.cbp.flipped_x_axis>` the 

136 telescope focal plane. 

137 cbpFlipX : `bool` 

138 :ref:`Flip <lsst.cbp.flipped_x_axis>` the CBP focal plane. 

139 

140 Returns 

141 ------- 

142 config : `lsst.cbp.CoordinateConverterConfig` 

143 Coordinate converter config. 

144 """ 

145 return CoordinateConverterConfig( 

146 telPupilOffset=101, 

147 telPupilDiameter=8500, 

148 telPupilObscurationDiameter=1000, 

149 telFocalPlaneDiameter=3000, 

150 telFlipX=telFlipX, 

151 telAzimuthOffsetDeg=-180, # offset=-180, scale=-1 for az=0 N, 90 E 

152 telAzimuthScale=-1, 

153 telAltitudeOffsetDeg=0, 

154 telAltitudeScale=1, 

155 telAltitudeLimitsDeg=(0, 89), 

156 telRotOffsetDeg=0, 

157 telRotScale=-1, 

158 defaultDetector="D0", 

159 cbpPosition=(10000, 3000, 5000), 

160 cbpFocalLength=635, # nominal value for LSST CBP 

161 cbpFlipX=cbpFlipX, 

162 cbpAzimuthOffsetDeg=-180, 

163 cbpAzimuthScale=-1, 

164 cbpAltitudeOffsetDeg=0, 

165 cbpAltitudeScale=1, 

166 cbpAltitudeLimitsDeg=(-70, 70), 

167 ) 

168 

169 def makeMaskInfo(self): 

170 """Make mask information. 

171 

172 Returns 

173 ------- 

174 maskInfo : `lsst.cbp.MaskInfo` 

175 Mask info. 

176 

177 Notes 

178 ----- 

179 The mask will have one hole per entry in self.holeFracPosList 

180 per detector. 

181 

182 self.cameraGeom and self.config must be set before calling this 

183 method. 

184 """ 

185 detectorBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

186 lsst.geom.Extent2I(self.detectorWidthPix, self.detectorHeightPix)) 

187 bboxd = lsst.geom.Box2D(detectorBBox) 

188 bboxdMin = np.array(bboxd.getMin()) 

189 bboxdDim = np.array(bboxd.getDimensions()) 

190 detectorPositions = [bboxdMin + bboxdDim*np.array(fracPos) for fracPos in self.holeFracPosList] 

191 holePositions = computeHolePositions( 

192 detectorNames=None, 

193 detectorPositions=detectorPositions, 

194 cameraGeom=self.cameraGeom, 

195 cbpFlipX=self.config.cbpFlipX, 

196 cbpFocalLength=self.config.cbpFocalLength, 

197 ) 

198 holeNames = ["beam{}".format(i) for i in range(len(holePositions))] 

199 return MaskInfo( 

200 name="test", 

201 holePositions=holePositions, 

202 holeNames=holeNames, 

203 defaultHole=0, 

204 ) 

205 

206 def makeCameraGeom(self): 

207 """Make a camera geometry. 

208 

209 Returns 

210 ------- 

211 cameraGeom : `lsst.afw.cameraGeom.Camera` 

212 Camera geometry. 

213 

214 Notes 

215 ----- 

216 There is one field per entry in self.detectorFracPosList 

217 with specifications set by self.detectorWidthPix, 

218 self.detectorHeightPix, and self.pixelSizeMm. 

219 

220 The plate scale is set by self.plateScale 

221 and the amount of optical distortion is fixed. 

222 

223 All detectors have the same shape (unlike LSST) and orientation 

224 (unlike HSC). Varying these is not necessary for testing the CBP 

225 and having all detectors the same simplifies the code. 

226 """ 

227 radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / self.plateScale.asRadians() 

228 fieldAngleToFocalPlane = makeRadialTransform(radialCoeff) 

229 focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() 

230 

231 cameraBuilder = Camera.Builder("testCamera") 

232 cameraBuilder.setTransformFromFocalPlaneTo(FIELD_ANGLE, focalPlaneToFieldAngle) 

233 ampBuilder = self._makeAmpBuilder() 

234 

235 for i, fpPos in enumerate(self.detectorFracPosList): 

236 detectorConfig = self._makeDetectorConfig(id=i, fpPos=fpPos) 

237 addDetectorBuilderFromConfig(cameraBuilder, detectorConfig, [ampBuilder], focalPlaneToFieldAngle) 

238 

239 return cameraBuilder.finish() 

240 

241 def _makeAmpBuilder(self): 

242 """Construct a trivial amplifier builder. 

243 

244 The CBP code does not care about the details of the amplifier, so this 

245 builder is as simple as possible: one amplifier that covers the whole 

246 CCD, with no overscan, and semi-plausible valus for everything else. 

247 

248 Returns 

249 ------- 

250 ampBuilder : `lsst.afw.cameraGeom.Amplifier.Builder` 

251 Amplifier builder. 

252 """ 

253 ampExtent = lsst.geom.Extent2I(self.detectorWidthPix, self.detectorHeightPix) 

254 ampBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), ampExtent) 

255 

256 ampBuilder = Amplifier.Builder() 

257 ampBuilder.setName("TestAmp") 

258 ampBuilder.setBBox(ampBBox) 

259 ampBuilder.setGain(1.8) 

260 ampBuilder.setReadNoise(3.9) 

261 ampBuilder.setReadoutCorner(ReadoutCorner.LL) 

262 

263 return ampBuilder 

264 

265 def _makeDetectorConfig(self, id, fpPos): 

266 """Make a detector config for one detector. 

267 

268 Parameters 

269 ---------- 

270 id : `int` 

271 Detector ID. 

272 fpPos : pair of `float` 

273 Focal plane position of detector, in units of detector 

274 width/height. For example: 

275 

276 - (0, 0) is a detector centered on the focal plane 

277 - (1, 0) is adjacent to a centered detector, 

278 in the direction of increasing focal plane x 

279 

280 Returns 

281 ------- 

282 detectorConfig : `lsst.afw.cameraGeom.DetectorConfig` 

283 Detector configuration for one detector. 

284 """ 

285 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

286 lsst.geom.Extent2I(self.detectorWidthPix, self.detectorHeightPix)) 

287 ctr = lsst.geom.Box2D(bbox).getCenter() 

288 pixelSizeMm = 0.01 

289 config = DetectorConfig() 

290 config.name = "D{}".format(id) 

291 config.id = id 

292 # detector serial number is not used by the CBP code, 

293 # but some value is required to construct a Detector 

294 config.serial = '0000011' 

295 config.detectorType = 0 

296 config.bbox_x0 = bbox.getMinX() 

297 config.bbox_x1 = bbox.getMaxX() 

298 config.bbox_y0 = bbox.getMinY() 

299 config.bbox_y1 = bbox.getMaxY() 

300 config.pixelSize_x = pixelSizeMm 

301 config.pixelSize_y = pixelSizeMm 

302 config.transformDict.nativeSys = 'Pixels' 

303 config.transformDict.transforms = None 

304 config.refpos_x = ctr[0] 

305 config.refpos_y = ctr[1] 

306 config.offset_x = fpPos[0] * pixelSizeMm * self.detectorWidthPix 

307 config.offset_y = fpPos[1] * pixelSizeMm * self.detectorHeightPix 

308 config.transposeDetector = False 

309 config.pitchDeg = 0.0 

310 config.yawDeg = 0.0 

311 config.rollDeg = 0.0 

312 return config