Coverage for python/lsst/obs/test/testCamera.py: 14%
90 statements
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-12 03:00 -0700
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-12 03:00 -0700
1# This file is part of obs_test.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://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 <http://www.gnu.org/licenses/>.
21#
22__all__ = ["TestCamera"]
24import numpy as np
26import lsst.afw.cameraGeom as cameraGeom
27import lsst.geom as geom
28import lsst.afw.geom as afwGeom
31class TestCamera(cameraGeom.Camera):
32 """A simple test Camera.
34 Notes
35 -----
36 The camera has one ccd with name "0".
37 That CCD has four amplifiers with names "00", "01", "10", and "11".
39 The camera is modeled after a small portion of the LSST sim
40 Summer 2012 camera: a single detector with four amplifiers,
41 consisting of raft 2,2 sensor 0,0, half of channels 0,0 0,1 1,0 and 1,1
42 (the half closest to the Y centerline).
44 Note that the Summer 2012 camera has one very weird feature:
45 the bias region (rawHOverscanBbox) is actually a prescan
46 (it appears before the data pixels).
48 A raw image has the sky in the same orientation on all amplifier
49 subregions, so no amplifier subregions need their pixels to be flipped.
51 Standard keys are:
53 * ``amp``: amplifier number: one of 00, 01, 10, 11
54 * ``ccd``: ccd number: always 0
55 * ``visit``: exposure number; test data includes one exposure
56 with visit=1
57 """
58 def __new__(cls):
59 plateScale = geom.Angle(20, geom.arcseconds) # plate scale, in angle on sky/mm
60 # Radial distortion is modeled as a radial polynomial that converts from focal plane (in mm)
61 # to field angle (in radians). Thus the coefficients are:
62 # C0: always 0, for continuity at the center of the focal plane; units are rad
63 # C1: 1/plateScale; units are rad/mm
64 # C2: usually 0; units are rad/mm^2
65 # C3: radial distortion; units are rad/mm^3
66 radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScale.asRadians()
67 fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeff)
68 focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted()
70 camera = cameraGeom.Camera.Builder("test")
71 cls._makeDetectors(camera, focalPlaneToFieldAngle)
72 camera.setTransformFromFocalPlaneTo(cameraGeom.FIELD_ANGLE, focalPlaneToFieldAngle)
73 return camera.finish()
75 def __init__(self):
76 pass
78 @classmethod
79 def _makeDetectors(cls, camera, focalPlaneToFieldAngle):
80 """Make a list of detectors
82 Parameters
83 ----------
84 camera : `lsst.afw.cameraGeom.camera.Builder`
85 Camera to append detectors to.
86 focalPlaneToFieldAngle : `lsst.afw.geom.TransformPoint2ToPoint2`
87 Transform from ``FOCAL_PLANE`` to ``FIELD_ANGLE`` coordinates
88 in the forward direction.
89 """
90 detectorConfigList = cls._makeDetectorConfigList()
91 for detectorConfig in detectorConfigList:
92 amplifiers = cls._makeAmplifierCatalog()
93 detBuilder = cameraGeom.addDetectorBuilderFromConfig(
94 camera,
95 detectorConfig,
96 amplifiers,
97 focalPlaneToFieldAngle,
98 )
99 if detBuilder is None:
100 raise RuntimeError("Could not add detector!")
102 @classmethod
103 def _makeDetectorConfigList(cls):
104 """Make a list of detector configs
106 Returns
107 -------
108 detectorConfigList : `list` of `lsst.afw.cameraGeom.DetectorConfig`
109 List of detector configs.
110 """
111 # this camera has one detector that corresponds to a subregion of lsstSim detector R:2,2 S:0,0
112 # with lower left corner 0, 1000 and dimensions 1018 x 2000
113 # i.e. half of each of the following channels: 0,0, 0,1, 1,0 and 1,1
114 detector0Config = cameraGeom.DetectorConfig()
115 detector0Config.name = '0'
116 detector0Config.id = 0
117 detector0Config.serial = '0000011'
118 detector0Config.detectorType = 0
119 detector0Config.bbox_x0 = 0
120 detector0Config.bbox_x1 = 1017
121 detector0Config.bbox_y0 = 0
122 detector0Config.bbox_y1 = 1999
123 detector0Config.pixelSize_x = 0.01
124 detector0Config.pixelSize_y = 0.01
125 detector0Config.transformDict.nativeSys = 'Pixels'
126 detector0Config.transformDict.transforms = None
127 detector0Config.refpos_x = 2035.5
128 detector0Config.refpos_y = 999.5
129 detector0Config.offset_x = -42.26073
130 detector0Config.offset_y = -42.21914
131 detector0Config.transposeDetector = False
132 detector0Config.pitchDeg = 0.0
133 detector0Config.yawDeg = 90.0
134 detector0Config.rollDeg = 0.0
135 return [detector0Config]
137 @classmethod
138 def _makeAmplifierCatalog(cls):
139 """Construct an amplifier info catalog
141 Returns
142 -------
143 ampCatalog : `List` of `lsst.afw.cameraGeom.Amplifier.Builder()
144 Amplifier information catalog.
146 Notes
147 -----
148 The LSSTSim S12 amplifiers are unusual in that they start with 4 pixels
149 of usable bias region (which is used to set rawHOverscanBbox, despite the name),
150 followed by the data. There is no other underscan or overscan.
151 """
152 xDataExtent = 509 # trimmed
153 yDataExtent = 1000
154 xBiasExtent = 4
155 xRawExtent = xDataExtent + xBiasExtent
156 yRawExtent = yDataExtent
157 readNoise = 3.975 # amplifier read noise, in e-
158 saturationLevel = 65535
159 linearityType = cameraGeom.NullLinearityType
160 linearityCoeffs = [0, 0, 0, 0]
162 ampCatalog = []
163 for ampX in (0, 1):
164 for ampY in (0, 1):
165 # amplifier gain (e-/ADU) and read noiuse (ADU/pixel) from lsstSim raw data
166 # note that obs_test amp <ampX><ampY> = lsstSim amp C<ampY>,<ampX> (axes are swapped)
167 gain = {
168 (0, 0): 1.7741, # C0,0
169 (0, 1): 1.65881, # C1,0
170 (1, 0): 1.74151, # C0,1
171 (1, 1): 1.67073, # C1,1
172 }[(ampX, ampY)]
173 readNoise = {
174 (0, 0): 3.97531706217237, # C0,0
175 (0, 1): 4.08263755342685, # C1,0
176 (1, 0): 4.02753931932633, # C0,1
177 (1, 1): 4.1890610691135, # C1,1
178 }[(ampX, ampY)]
179 amplifier = cameraGeom.Amplifier.Builder()
180 amplifier.setName("%d%d" % (ampX, ampY))
181 amplifier.setBBox(geom.Box2I(
182 geom.Point2I(ampX * xDataExtent, ampY * yDataExtent),
183 geom.Extent2I(xDataExtent, yDataExtent),
184 ))
186 x0Raw = ampX * xRawExtent
187 y0Raw = ampY * yRawExtent
189 # bias region (which is prescan, in this case) is before the data
190 readCorner = cameraGeom.ReadoutCorner.LL
191 x0Bias = x0Raw
192 x0Data = x0Bias + xBiasExtent
194 amplifier.setRawBBox(geom.Box2I(
195 geom.Point2I(x0Raw, y0Raw),
196 geom.Extent2I(xRawExtent, yRawExtent),
197 ))
198 amplifier.setRawDataBBox(geom.Box2I(
199 geom.Point2I(x0Data, y0Raw),
200 geom.Extent2I(xDataExtent, yDataExtent),
201 ))
202 amplifier.setRawHorizontalOverscanBBox(geom.Box2I(
203 geom.Point2I(x0Bias, y0Raw),
204 geom.Extent2I(xBiasExtent, yRawExtent),
205 ))
206 amplifier.setRawXYOffset(geom.Extent2I(x0Raw, y0Raw))
207 amplifier.setReadoutCorner(readCorner)
208 amplifier.setGain(gain)
209 amplifier.setReadNoise(readNoise)
210 amplifier.setSaturation(saturationLevel)
211 amplifier.setSuspectLevel(float("nan"))
212 amplifier.setLinearityCoeffs([float(val) for val in linearityCoeffs])
213 amplifier.setLinearityType(linearityType)
214 # amplifier.setHasRawInfo(True)
215 amplifier.setRawFlipX(False)
216 amplifier.setRawFlipY(False)
217 amplifier.setRawVerticalOverscanBBox(geom.Box2I()) # no vertical overscan
218 amplifier.setRawPrescanBBox(geom.Box2I()) # no horizontal prescan
219 ampCatalog.append(amplifier)
220 return ampCatalog