Coverage for python/lsst/cbp/testUtils.py: 18%
84 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-23 03:28 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-23 03:28 -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"""
23__all__ = ["SampleCoordinateConverter"]
25import numpy as np
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
37class SampleCoordinateConverter:
38 """An object containing a CoordinateConverter and the information
39 used to create it.
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::
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 )
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))`.
63 Notes
64 -----
65 **Attributes**
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 """
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
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 )
129 def makeCoordinateConverterConfig(self, telFlipX, cbpFlipX):
130 """Make a coordinate converter config.
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.
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 )
169 def makeMaskInfo(self):
170 """Make mask information.
172 Returns
173 -------
174 maskInfo : `lsst.cbp.MaskInfo`
175 Mask info.
177 Notes
178 -----
179 The mask will have one hole per entry in self.holeFracPosList
180 per detector.
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 )
206 def makeCameraGeom(self):
207 """Make a camera geometry.
209 Returns
210 -------
211 cameraGeom : `lsst.afw.cameraGeom.Camera`
212 Camera geometry.
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.
220 The plate scale is set by self.plateScale
221 and the amount of optical distortion is fixed.
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()
231 cameraBuilder = Camera.Builder("testCamera")
232 cameraBuilder.setTransformFromFocalPlaneTo(FIELD_ANGLE, focalPlaneToFieldAngle)
233 ampBuilder = self._makeAmpBuilder()
235 for i, fpPos in enumerate(self.detectorFracPosList):
236 detectorConfig = self._makeDetectorConfig(id=i, fpPos=fpPos)
237 addDetectorBuilderFromConfig(cameraBuilder, detectorConfig, [ampBuilder], focalPlaneToFieldAngle)
239 return cameraBuilder.finish()
241 def _makeAmpBuilder(self):
242 """Construct a trivial amplifier builder.
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.
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)
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)
263 return ampBuilder
265 def _makeDetectorConfig(self, id, fpPos):
266 """Make a detector config for one detector.
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:
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
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