LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
LSSTDataManagementBasePackage
cameraFactory.py
Go to the documentation of this file.
1 # This file is part of afw.
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__ = ["makeCameraFromPath", "makeCameraFromCatalogs",
23  "makeDetector", "copyDetector"]
24 
25 import os.path
26 import lsst.geom
27 from lsst.afw.table import AmpInfoCatalog
28 from .cameraGeomLib import FOCAL_PLANE, FIELD_ANGLE, PIXELS, TAN_PIXELS, ACTUAL_PIXELS, CameraSys, \
29  Detector, DetectorType, Orientation, TransformMap
30 from .camera import Camera
31 from .makePixelToTanPixel import makePixelToTanPixel
32 from .pupil import PupilFactory
33 
34 cameraSysList = [FIELD_ANGLE, FOCAL_PLANE, PIXELS, TAN_PIXELS, ACTUAL_PIXELS]
35 cameraSysMap = dict((sys.getSysName(), sys) for sys in cameraSysList)
36 
37 
38 def makeDetectorData(detectorConfig, ampInfoCatalog, focalPlaneToField):
39  """Build a dictionary of Detector constructor keyword arguments.
40 
41  The returned dictionary can be passed as keyword arguments to the Detector
42  constructor, providing all required arguments. However, using these
43  arguments directly constructs a Detector with knowledge of only the
44  coordinate systems that are *directly* mapped to its own PIXELS coordinate
45  system. To construct Detectors with a shared TransformMap for the full
46  Camera, use makeCameraFromCatalogs or makeCameraFromPath instead of
47  calling this function or makeDetector directly.
48 
49  Parameters
50  ----------
51  detectorConfig : `lsst.pex.config.Config`
52  Configuration for this detector.
53  ampInfoCatalog : `lsst.afw.table.AmpInfoCatalog`
54  amplifier information for this detector
55  focalPlaneToField : `lsst.afw.geom.TransformPoint2ToPoint2`
56  FOCAL_PLANE to FIELD_ANGLE Transform
57 
58  Returns
59  -------
60  data : `dict`
61  Contains the following keys: name, id, type, serial, bbox, orientation,
62  pixelSize, transforms, ampInfoCatalog, and optionally crosstalk.
63  The transforms key is a dictionary whose values are Transforms that map
64  the Detector's PIXEL coordinate system to the CameraSys in the key.
65  """
66 
67  data = dict(
68  name=detectorConfig.name,
69  id=detectorConfig.id,
70  type=DetectorType(detectorConfig.detectorType),
71  physicalType=detectorConfig.physicalType,
72  serial=detectorConfig.serial,
73  ampInfoCatalog=ampInfoCatalog,
74  orientation=makeOrientation(detectorConfig),
75  pixelSize=lsst.geom.Extent2D(detectorConfig.pixelSize_x, detectorConfig.pixelSize_y),
76  bbox=lsst.geom.Box2I(
77  minimum=lsst.geom.Point2I(detectorConfig.bbox_x0, detectorConfig.bbox_y0),
78  maximum=lsst.geom.Point2I(detectorConfig.bbox_x1, detectorConfig.bbox_y1),
79  ),
80  )
81 
82  transforms = makeTransformDict(detectorConfig.transformDict.transforms)
83  transforms[FOCAL_PLANE] = data["orientation"].makePixelFpTransform(data["pixelSize"])
84 
85  tanPixSys = CameraSys(TAN_PIXELS, detectorConfig.name)
86  transforms[tanPixSys] = makePixelToTanPixel(
87  bbox=data["bbox"],
88  orientation=data["orientation"],
89  focalPlaneToField=focalPlaneToField,
90  pixelSizeMm=data["pixelSize"],
91  )
92 
93  data["transforms"] = transforms
94 
95  crosstalk = detectorConfig.getCrosstalk(len(ampInfoCatalog))
96  if crosstalk is not None:
97  data["crosstalk"] = crosstalk
98 
99  return data
100 
101 
102 def makeDetector(detectorConfig, ampInfoCatalog, focalPlaneToField):
103  """Make a Detector instance from a detector config and amp info catalog
104 
105  Parameters
106  ----------
107  detectorConfig : `lsst.pex.config.Config`
108  Configuration for this detector.
109  ampInfoCatalog : `lsst.afw.table.AmpInfoCatalog`
110  amplifier information for this detector
111  focalPlaneToField : `lsst.afw.geom.TransformPoint2ToPoint2`
112  FOCAL_PLANE to FIELD_ANGLE Transform
113 
114  Returns
115  -------
116  detector : `lsst.afw.cameraGeom.Detector`
117  New Detector instance.
118  """
119  data = makeDetectorData(detectorConfig, ampInfoCatalog, focalPlaneToField)
120  return Detector(**data)
121 
122 
123 def copyDetector(detector, ampInfoCatalog=None):
124  """Return a copy of a Detector with possibly-updated amplifier information.
125 
126  No deep copies are made; the input transformDict is used unmodified
127 
128  Parameters
129  ----------
130  detector : `lsst.afw.cameraGeom.Detector`
131  The Detector to clone
132  ampInfoCatalog The ampInfoCatalog to use; default use original
133 
134  Returns
135  -------
136  detector : `lsst.afw.cameraGeom.Detector`
137  New Detector instance.
138  """
139  if ampInfoCatalog is None:
140  ampInfoCatalog = detector.getAmpInfoCatalog()
141 
142  return Detector(detector.getName(), detector.getId(), detector.getType(),
143  detector.getSerial(), detector.getBBox(),
144  ampInfoCatalog, detector.getOrientation(), detector.getPixelSize(),
145  detector.getTransformMap(), detector.getCrosstalk(), detector.getPhysicalType())
146 
147 
148 def makeOrientation(detectorConfig):
149  """Make an Orientation instance from a detector config
150 
151  Parameters
152  ----------
153  detectorConfig : `lsst.pex.config.Config`
154  Configuration for this detector.
155 
156  Returns
157  -------
158  orientation : `lsst.afw.cameraGeom.Orientation`
159  Location and rotation of the Detector.
160  """
161  offset = lsst.geom.Point2D(detectorConfig.offset_x, detectorConfig.offset_y)
162  refPos = lsst.geom.Point2D(detectorConfig.refpos_x, detectorConfig.refpos_y)
163  yaw = lsst.geom.Angle(detectorConfig.yawDeg, lsst.geom.degrees)
164  pitch = lsst.geom.Angle(detectorConfig.pitchDeg, lsst.geom.degrees)
165  roll = lsst.geom.Angle(detectorConfig.rollDeg, lsst.geom.degrees)
166  return Orientation(offset, refPos, yaw, pitch, roll)
167 
168 
169 def makeTransformDict(transformConfigDict):
170  """Make a dictionary of CameraSys: lsst.afw.geom.Transform from a config dict.
171 
172  Parameters
173  ----------
174  transformConfigDict : value obtained from a `lsst.pex.config.ConfigDictField`
175  registry; keys are camera system names.
176 
177  Returns
178  -------
179  transforms : `dict`
180  A dict of CameraSys or CameraSysPrefix: lsst.afw.geom.Transform
181  """
182  resMap = dict()
183  if transformConfigDict is not None:
184  for key in transformConfigDict:
185  transform = transformConfigDict[key].transform.apply()
186  resMap[CameraSys(key)] = transform
187  return resMap
188 
189 
190 def makeCameraFromPath(cameraConfig, ampInfoPath, shortNameFunc,
191  pupilFactoryClass=PupilFactory):
192  """Make a Camera instance from a directory of ampInfo files
193 
194  The directory must contain one ampInfo fits file for each detector in cameraConfig.detectorList.
195  The name of each ampInfo file must be shortNameFunc(fullDetectorName) + ".fits".
196 
197  Parameters
198  ----------
199  cameraConfig : `CameraConfig`
200  Config describing camera and its detectors.
201  ampInfoPath : `str`
202  Path to ampInfo data files.
203  shortNameFunc : callable
204  A function that converts a long detector name to a short one.
205  pupilFactoryClass : `type`, optional
206  Class to attach to camera; default is `lsst.afw.cameraGeom.PupilFactory`.
207 
208  Returns
209  -------
210  camera : `lsst.afw.cameraGeom.Camera`
211  New Camera instance.
212  """
213  ampInfoCatDict = dict()
214  for detectorConfig in cameraConfig.detectorList.values():
215  shortName = shortNameFunc(detectorConfig.name)
216  ampCatPath = os.path.join(ampInfoPath, shortName + ".fits")
217  ampInfoCatalog = AmpInfoCatalog.readFits(ampCatPath)
218  ampInfoCatDict[detectorConfig.name] = ampInfoCatalog
219 
220  return makeCameraFromCatalogs(cameraConfig, ampInfoCatDict, pupilFactoryClass)
221 
222 
223 def makeCameraFromCatalogs(cameraConfig, ampInfoCatDict,
224  pupilFactoryClass=PupilFactory):
225  """Construct a Camera instance from a dictionary of detector name: AmpInfoCatalog
226 
227  Parameters
228  ----------
229  cameraConfig : `CameraConfig`
230  Config describing camera and its detectors.
231  ampInfoCatDict : `dict`
232  A dictionary of detector name: AmpInfoCatalog
233  pupilFactoryClass : `type`, optional
234  Class to attach to camera; `lsst.default afw.cameraGeom.PupilFactory`.
235 
236  Returns
237  -------
238  camera : `lsst.afw.cameraGeom.Camera`
239  New Camera instance.
240  """
241  nativeSys = cameraSysMap[cameraConfig.transformDict.nativeSys]
242 
243  # nativeSys=FOCAL_PLANE seems to be assumed in various places in this file
244  # (e.g. the definition of TAN_PIXELS), despite CameraConfig providing the
245  # illusion that it's configurable.
246  # Note that we can't actually get rid of the nativeSys config option
247  # without breaking lots of on-disk camera configs.
248  assert nativeSys == FOCAL_PLANE, "Cameras with nativeSys != FOCAL_PLANE are not supported."
249 
250  transformDict = makeTransformDict(cameraConfig.transformDict.transforms)
251  focalPlaneToField = transformDict[FIELD_ANGLE]
252  transformMapBuilder = TransformMap.Builder(nativeSys)
253  transformMapBuilder.connect(transformDict)
254 
255  # First pass: build a list of all Detector ctor kwargs, minus the
256  # transformMap (which needs information from all Detectors).
257  detectorData = []
258  for detectorConfig in cameraConfig.detectorList.values():
259 
260  # Get kwargs that could be used to construct each Detector
261  # if we didn't care about giving each of them access to
262  # all of the transforms.
263  thisDetectorData = makeDetectorData(
264  detectorConfig=detectorConfig,
265  ampInfoCatalog=ampInfoCatDict[detectorConfig.name],
266  focalPlaneToField=focalPlaneToField,
267  )
268 
269  # Pull the transforms dictionary out of the data dict; we'll replace
270  # it with a TransformMap argument later.
271  thisDetectorTransforms = thisDetectorData.pop("transforms")
272 
273  # Save the rest of the Detector data dictionary for later
274  detectorData.append(thisDetectorData)
275 
276  # For reasons I don't understand, some obs_ packages (e.g. HSC) set
277  # nativeSys to None for their detectors (which doesn't seem to be
278  # permitted by the config class!), but they really mean PIXELS. For
279  # backwards compatibility we use that as the default...
280  detectorNativeSysPrefix = cameraSysMap.get(detectorConfig.transformDict.nativeSys, PIXELS)
281 
282  # ...well, actually, it seems that we've always assumed down in C++
283  # that the answer is always PIXELS without ever checking that it is.
284  # So let's assert that it is, since there are hints all over this file
285  # (e.g. the definition of TAN_PIXELS) that other parts of the codebase
286  # have regularly made that assumption as well. Note that we can't
287  # actually get rid of the nativeSys config option without breaking
288  # lots of on-disk camera configs.
289  assert detectorNativeSysPrefix == PIXELS, "Detectors with nativeSys != PIXELS are not supported."
290  detectorNativeSys = CameraSys(detectorNativeSysPrefix, detectorConfig.name)
291 
292  # Add this detector's transform dict to the shared TransformMapBuilder
293  transformMapBuilder.connect(detectorNativeSys, thisDetectorTransforms)
294 
295  # Now that we've collected all of the Transforms, we can finally build the
296  # (immutable) TransformMap.
297  transformMap = transformMapBuilder.build()
298 
299  # Second pass through the detectorConfigs: actually make Detector instances
300  detectorList = [Detector(transformMap=transformMap, **kw) for kw in detectorData]
301 
302  return Camera(cameraConfig.name, detectorList, transformMap, pupilFactoryClass)
def copyDetector(detector, ampInfoCatalog=None)
def makeDetectorData(detectorConfig, ampInfoCatalog, focalPlaneToField)
def makeCameraFromCatalogs(cameraConfig, ampInfoCatDict, pupilFactoryClass=PupilFactory)
A class representing an angle.
Definition: Angle.h:127
Describe a detector&#39;s orientation in the focal plane.
Definition: Orientation.h:52
Helper class used to incrementally construct TransformMap instances.
Definition: TransformMap.h:239
def makeCameraFromPath(cameraConfig, ampInfoPath, shortNameFunc, pupilFactoryClass=PupilFactory)
Information about a CCD or other imaging detector.
Definition: Detector.h:62
def makePixelToTanPixel(bbox, orientation, focalPlaneToField, pixelSizeMm)
def makeTransformDict(transformConfigDict)
def makeDetector(detectorConfig, ampInfoCatalog, focalPlaneToField)
An integer coordinate rectangle.
Definition: Box.h:54