lsst.obs.base  16.0-15-gd0383af
yamlCamera.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2017 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import yaml
23 
24 import numpy as np
25 import lsst.afw.cameraGeom as cameraGeom
26 import lsst.afw.geom as afwGeom
27 from lsst.afw.table import AmpInfoCatalog, AmpInfoTable
28 from lsst.afw.cameraGeom.cameraFactory import makeDetector
29 
30 
31 class YamlCamera(cameraGeom.Camera):
32  """The Commissioning Camera (comCam)
33 
34  Parameters
35  ----------
36  cameraYamlFile : `str`
37  Camera description YAML file.
38  """
39 
40  def __init__(self, cameraYamlFile):
41  with open(cameraYamlFile) as fd:
42  cameraParams = yaml.load(fd, Loader=yaml.Loader)
43 
44  plateScale = afwGeom.Angle(cameraParams["plateScale"], afwGeom.arcseconds)
45  # radial coefficients of the form [0, no units, 1/rad but usually 0, 1/rad^2, ...]
46  # Radial distortion is modeled as a radial polynomial that converts from focal plane radius (in mm)
47  # to field angle (in radians). The coefficients are divided by the plate scale (in mm/radians)
48  # meaning that C1 is always 1.
49  radialCoeffs = np.array(cameraParams["radialCoeffs"])/plateScale.asRadians()
50  fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeffs)
51  focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted()
52  cameraTransformMap = cameraGeom.TransformMap(cameraGeom.FOCAL_PLANE,
53  {cameraGeom.FIELD_ANGLE: focalPlaneToFieldAngle})
54  detectorList = self._makeDetectorList(cameraParams["CCDs"], focalPlaneToFieldAngle)
55  cameraGeom.Camera.__init__(self, cameraParams["name"], detectorList, cameraTransformMap)
56 
57  def _makeDetectorList(self, ccdParams, focalPlaneToFieldAngle):
58  """Make a list of detectors
59 
60  Parameters
61  ----------
62  ccdParams : `dict`
63  Dict of YAML descriptions of CCDs.
64  focalPlaneToFieldAngle : `lsst.afw.geom.TransformPoint2ToPoint2`
65  Angle from FOCAL_PLANE to FIELD_ANGLE coordinates.
66 
67  Returns
68  -------
69  `list` of `lsst.afw.cameraGeom.Detector`
70  list of detectors in this camera.
71  """
72  detectorList = []
73  detectorConfigList = self._makeDetectorConfigList(ccdParams)
74  for ccd, detectorConfig in zip(ccdParams.values(), detectorConfigList):
75  ampInfoCatalog = self._makeAmpInfoCatalog(ccd)
76  detector = makeDetector(detectorConfig, ampInfoCatalog, focalPlaneToFieldAngle)
77  detectorList.append(detector)
78  return detectorList
79 
80  def _makeDetectorConfigList(self, ccdParams):
81  """Make a list of detector configs
82 
83  Returns
84  -------
85  `list` of `lsst.afw.cameraGeom.DetectorConfig`
86  A list of detector configs.
87  """
88  detectorConfigs = []
89  for name, ccd in ccdParams.items():
90  detectorConfig = cameraGeom.DetectorConfig()
91  detectorConfigs.append(detectorConfig)
92 
93  detectorConfig.name = name
94  detectorConfig.id = ccd['id']
95  detectorConfig.serial = ccd['serial']
96  detectorConfig.detectorType = ccd['detectorType']
97  # This is the orientation we need to put the serial direction along the x-axis
98  detectorConfig.bbox_x0, detectorConfig.bbox_y0 = ccd['bbox'][0]
99  detectorConfig.bbox_x1, detectorConfig.bbox_y1 = ccd['bbox'][1]
100  detectorConfig.pixelSize_x, detectorConfig.pixelSize_y = ccd['pixelSize']
101  detectorConfig.transformDict.nativeSys = ccd['transformDict']['nativeSys']
102  transforms = ccd['transformDict']['transforms']
103  detectorConfig.transformDict.transforms = None if transforms == 'None' else transforms
104  detectorConfig.refpos_x, detectorConfig.refpos_y = ccd['refpos']
105  detectorConfig.offset_x, detectorConfig.offset_y = ccd['offset']
106  detectorConfig.transposeDetector = ccd['transposeDetector']
107  detectorConfig.pitchDeg = ccd['pitch']
108  detectorConfig.yawDeg = ccd['yaw']
109  detectorConfig.rollDeg = ccd['roll']
110  if 'crosstalk' in ccd:
111  detectorConfig.crosstalk = ccd['crosstalk']
112 
113  return detectorConfigs
114 
115  @staticmethod
116  def _makeBBoxFromList(ylist):
117  """Given a list [(x0, y0), (xsize, ysize)], probably from a yaml file,
118  return a BoxI
119  """
120  (x0, y0), (xsize, ysize) = ylist
121  return afwGeom.BoxI(afwGeom.PointI(x0, y0), afwGeom.ExtentI(xsize, ysize))
122 
123  def _makeAmpInfoCatalog(self, ccd):
124  """Construct an amplifier info catalog
125  """
126  # Much of this will need to be filled in when we know it.
127  assert len(ccd['amplifiers']) > 0
128  amp = list(ccd['amplifiers'].values())[0]
129 
130  rawBBox = self._makeBBoxFromList(amp['rawBBox']) # total in file
131  xRawExtent, yRawExtent = rawBBox.getDimensions()
132 
133  from lsst.afw.table import LL, LR, UL, UR
134  readCorners = dict(LL=LL, LR=LR, UL=UL, UR=UR)
135 
136  schema = AmpInfoTable.makeMinimalSchema()
137 
138  linThreshKey = schema.addField('linearityThreshold', type=float)
139  linMaxKey = schema.addField('linearityMaximum', type=float)
140  linUnitsKey = schema.addField('linearityUnits', type=str, size=9)
141  hduKey = schema.addField('hdu', type=np.int32)
142  # end placeholder
143  self.ampInfoDict = {}
144  ampCatalog = AmpInfoCatalog(schema)
145  for name, amp in sorted(ccd['amplifiers'].items(), key=lambda x: x[1]['hdu']):
146  record = ampCatalog.addNew()
147  record.setName(name)
148  record.set(hduKey, amp['hdu'])
149 
150  ix, iy = amp['ixy']
151  perAmpData = amp['perAmpData']
152  if perAmpData:
153  x0, y0 = 0, 0 # origin of data within each amp image
154  else:
155  x0, y0 = ix*xRawExtent, iy*yRawExtent
156 
157  rawDataBBox = self._makeBBoxFromList(amp['rawDataBBox']) # Photosensitive area
158  xDataExtent, yDataExtent = rawDataBBox.getDimensions()
159  record.setBBox(afwGeom.BoxI(
160  afwGeom.PointI(ix*xDataExtent, iy*yDataExtent), rawDataBBox.getDimensions()))
161 
162  rawBBox = self._makeBBoxFromList(amp['rawBBox'])
163  rawBBox.shift(afwGeom.ExtentI(x0, y0))
164  record.setRawBBox(rawBBox)
165 
166  rawDataBBox = self._makeBBoxFromList(amp['rawDataBBox'])
167  rawDataBBox.shift(afwGeom.ExtentI(x0, y0))
168  record.setRawDataBBox(rawDataBBox)
169 
170  rawSerialOverscanBBox = self._makeBBoxFromList(amp['rawSerialOverscanBBox'])
171  rawSerialOverscanBBox.shift(afwGeom.ExtentI(x0, y0))
172  record.setRawHorizontalOverscanBBox(rawSerialOverscanBBox)
173 
174  rawParallelOverscanBBox = self._makeBBoxFromList(amp['rawParallelOverscanBBox'])
175  rawParallelOverscanBBox.shift(afwGeom.ExtentI(x0, y0))
176  record.setRawVerticalOverscanBBox(rawParallelOverscanBBox)
177 
178  rawSerialPrescanBBox = self._makeBBoxFromList(amp['rawSerialPrescanBBox'])
179  rawSerialPrescanBBox.shift(afwGeom.ExtentI(x0, y0))
180  record.setRawPrescanBBox(rawSerialPrescanBBox)
181 
182  if perAmpData:
183  record.setRawXYOffset(afwGeom.Extent2I(ix*xRawExtent, iy*yRawExtent))
184  else:
185  record.setRawXYOffset(afwGeom.Extent2I(0, 0))
186 
187  record.setReadoutCorner(readCorners[amp['readCorner']])
188  record.setGain(amp['gain'])
189  record.setReadNoise(amp['readNoise'])
190  record.setSaturation(amp['saturation'])
191  record.setHasRawInfo(True)
192  # flip data when assembling if needs be (e.g. data from the serial at the top of a CCD)
193  flipX, flipY = amp.get("flipXY")
194 
195  record.setRawFlipX(flipX)
196  record.setRawFlipY(flipY)
197  # linearity placeholder stuff
198  record.setLinearityCoeffs([float(val) for val in amp['linearityCoeffs']])
199  record.setLinearityType(amp['linearityType'])
200  record.set(linThreshKey, float(amp['linearityThreshold']))
201  record.set(linMaxKey, float(amp['linearityMax']))
202  record.set(linUnitsKey, "DN")
203  return ampCatalog
def _makeDetectorList(self, ccdParams, focalPlaneToFieldAngle)
Definition: yamlCamera.py:57
def __init__(self, cameraYamlFile)
Definition: yamlCamera.py:40
def _makeDetectorConfigList(self, ccdParams)
Definition: yamlCamera.py:80