LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
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  serial=detectorConfig.serial,
72  ampInfoCatalog=ampInfoCatalog,
73  orientation=makeOrientation(detectorConfig),
74  pixelSize=lsst.geom.Extent2D(detectorConfig.pixelSize_x, detectorConfig.pixelSize_y),
75  bbox=lsst.geom.Box2I(
76  minimum=lsst.geom.Point2I(detectorConfig.bbox_x0, detectorConfig.bbox_y0),
77  maximum=lsst.geom.Point2I(detectorConfig.bbox_x1, detectorConfig.bbox_y1),
78  ),
79  )
80 
81  transforms = makeTransformDict(detectorConfig.transformDict.transforms)
82  transforms[FOCAL_PLANE] = data["orientation"].makePixelFpTransform(data["pixelSize"])
83 
84  tanPixSys = CameraSys(TAN_PIXELS, detectorConfig.name)
85  transforms[tanPixSys] = makePixelToTanPixel(
86  bbox=data["bbox"],
87  orientation=data["orientation"],
88  focalPlaneToField=focalPlaneToField,
89  pixelSizeMm=data["pixelSize"],
90  )
91 
92  data["transforms"] = transforms
93 
94  crosstalk = detectorConfig.getCrosstalk(len(ampInfoCatalog))
95  if crosstalk is not None:
96  data["crosstalk"] = crosstalk
97 
98  return data
99 
100 
101 def makeDetector(detectorConfig, ampInfoCatalog, focalPlaneToField):
102  """Make a Detector instance from a detector config and amp info catalog
103 
104  Parameters
105  ----------
106  detectorConfig : `lsst.pex.config.Config`
107  Configuration for this detector.
108  ampInfoCatalog : `lsst.afw.table.AmpInfoCatalog`
109  amplifier information for this detector
110  focalPlaneToField : `lsst.afw.geom.TransformPoint2ToPoint2`
111  FOCAL_PLANE to FIELD_ANGLE Transform
112 
113  Returns
114  -------
115  detector : `lsst.afw.cameraGeom.Detector`
116  New Detector instance.
117  """
118  data = makeDetectorData(detectorConfig, ampInfoCatalog, focalPlaneToField)
119  return Detector(**data)
120 
121 
122 def copyDetector(detector, ampInfoCatalog=None):
123  """Return a copy of a Detector with possibly-updated amplifier information.
124 
125  No deep copies are made; the input transformDict is used unmodified
126 
127  Parameters
128  ----------
129  detector : `lsst.afw.cameraGeom.Detector`
130  The Detector to clone
131  ampInfoCatalog The ampInfoCatalog to use; default use original
132 
133  Returns
134  -------
135  detector : `lsst.afw.cameraGeom.Detector`
136  New Detector instance.
137  """
138  if ampInfoCatalog is None:
139  ampInfoCatalog = detector.getAmpInfoCatalog()
140 
141  return Detector(detector.getName(), detector.getId(), detector.getType(),
142  detector.getSerial(), detector.getBBox(),
143  ampInfoCatalog, detector.getOrientation(), detector.getPixelSize(),
144  detector.getTransformMap(), detector.getCrosstalk())
145 
146 
147 def makeOrientation(detectorConfig):
148  """Make an Orientation instance from a detector config
149 
150  Parameters
151  ----------
152  detectorConfig : `lsst.pex.config.Config`
153  Configuration for this detector.
154 
155  Returns
156  -------
157  orientation : `lsst.afw.cameraGeom.Orientation`
158  Location and rotation of the Detector.
159  """
160  offset = lsst.geom.Point2D(detectorConfig.offset_x, detectorConfig.offset_y)
161  refPos = lsst.geom.Point2D(detectorConfig.refpos_x, detectorConfig.refpos_y)
162  yaw = lsst.geom.Angle(detectorConfig.yawDeg, lsst.geom.degrees)
163  pitch = lsst.geom.Angle(detectorConfig.pitchDeg, lsst.geom.degrees)
164  roll = lsst.geom.Angle(detectorConfig.rollDeg, lsst.geom.degrees)
165  return Orientation(offset, refPos, yaw, pitch, roll)
166 
167 
168 def makeTransformDict(transformConfigDict):
169  """Make a dictionary of CameraSys: lsst.afw.geom.Transform from a config dict.
170 
171  Parameters
172  ----------
173  transformConfigDict : value obtained from a `lsst.pex.config.ConfigDictField`
174  registry; keys are camera system names.
175 
176  Returns
177  -------
178  transforms : `dict`
179  A dict of CameraSys or CameraSysPrefix: lsst.afw.geom.Transform
180  """
181  resMap = dict()
182  if transformConfigDict is not None:
183  for key in transformConfigDict:
184  transform = transformConfigDict[key].transform.apply()
185  resMap[CameraSys(key)] = transform
186  return resMap
187 
188 
189 def makeCameraFromPath(cameraConfig, ampInfoPath, shortNameFunc,
190  pupilFactoryClass=PupilFactory):
191  """Make a Camera instance from a directory of ampInfo files
192 
193  The directory must contain one ampInfo fits file for each detector in cameraConfig.detectorList.
194  The name of each ampInfo file must be shortNameFunc(fullDetectorName) + ".fits".
195 
196  Parameters
197  ----------
198  cameraConfig : `CameraConfig`
199  Config describing camera and its detectors.
200  ampInfoPath : `str`
201  Path to ampInfo data files.
202  shortNameFunc : callable
203  A function that converts a long detector name to a short one.
204  pupilFactoryClass : `type`, optional
205  Class to attach to camera; default is `lsst.afw.cameraGeom.PupilFactory`.
206 
207  Returns
208  -------
209  camera : `lsst.afw.cameraGeom.Camera`
210  New Camera instance.
211  """
212  ampInfoCatDict = dict()
213  for detectorConfig in cameraConfig.detectorList.values():
214  shortName = shortNameFunc(detectorConfig.name)
215  ampCatPath = os.path.join(ampInfoPath, shortName + ".fits")
216  ampInfoCatalog = AmpInfoCatalog.readFits(ampCatPath)
217  ampInfoCatDict[detectorConfig.name] = ampInfoCatalog
218 
219  return makeCameraFromCatalogs(cameraConfig, ampInfoCatDict, pupilFactoryClass)
220 
221 
222 def makeCameraFromCatalogs(cameraConfig, ampInfoCatDict,
223  pupilFactoryClass=PupilFactory):
224  """Construct a Camera instance from a dictionary of detector name: AmpInfoCatalog
225 
226  Parameters
227  ----------
228  cameraConfig : `CameraConfig`
229  Config describing camera and its detectors.
230  ampInfoCatDict : `dict`
231  A dictionary of detector name: AmpInfoCatalog
232  pupilFactoryClass : `type`, optional
233  Class to attach to camera; `lsst.default afw.cameraGeom.PupilFactory`.
234 
235  Returns
236  -------
237  camera : `lsst.afw.cameraGeom.Camera`
238  New Camera instance.
239  """
240  nativeSys = cameraSysMap[cameraConfig.transformDict.nativeSys]
241 
242  # nativeSys=FOCAL_PLANE seems to be assumed in various places in this file
243  # (e.g. the definition of TAN_PIXELS), despite CameraConfig providing the
244  # illusion that it's configurable.
245  # Note that we can't actually get rid of the nativeSys config option
246  # without breaking lots of on-disk camera configs.
247  assert nativeSys == FOCAL_PLANE, "Cameras with nativeSys != FOCAL_PLANE are not supported."
248 
249  transformDict = makeTransformDict(cameraConfig.transformDict.transforms)
250  focalPlaneToField = transformDict[FIELD_ANGLE]
251  transformMapBuilder = TransformMap.Builder(nativeSys)
252  transformMapBuilder.connect(transformDict)
253 
254  # First pass: build a list of all Detector ctor kwargs, minus the
255  # transformMap (which needs information from all Detectors).
256  detectorData = []
257  for detectorConfig in cameraConfig.detectorList.values():
258 
259  # Get kwargs that could be used to construct each Detector
260  # if we didn't care about giving each of them access to
261  # all of the transforms.
262  thisDetectorData = makeDetectorData(
263  detectorConfig=detectorConfig,
264  ampInfoCatalog=ampInfoCatDict[detectorConfig.name],
265  focalPlaneToField=focalPlaneToField,
266  )
267 
268  # Pull the transforms dictionary out of the data dict; we'll replace
269  # it with a TransformMap argument later.
270  thisDetectorTransforms = thisDetectorData.pop("transforms")
271 
272  # Save the rest of the Detector data dictionary for later
273  detectorData.append(thisDetectorData)
274 
275  # For reasons I don't understand, some obs_ packages (e.g. HSC) set
276  # nativeSys to None for their detectors (which doesn't seem to be
277  # permitted by the config class!), but they really mean PIXELS. For
278  # backwards compatibility we use that as the default...
279  detectorNativeSysPrefix = cameraSysMap.get(detectorConfig.transformDict.nativeSys, PIXELS)
280 
281  # ...well, actually, it seems that we've always assumed down in C++
282  # that the answer is always PIXELS without ever checking that it is.
283  # So let's assert that it is, since there are hints all over this file
284  # (e.g. the definition of TAN_PIXELS) that other parts of the codebase
285  # have regularly made that assumption as well. Note that we can't
286  # actually get rid of the nativeSys config option without breaking
287  # lots of on-disk camera configs.
288  assert detectorNativeSysPrefix == PIXELS, "Detectors with nativeSys != PIXELS are not supported."
289  detectorNativeSys = CameraSys(detectorNativeSysPrefix, detectorConfig.name)
290 
291  # Add this detector's transform dict to the shared TransformMapBuilder
292  transformMapBuilder.connect(detectorNativeSys, thisDetectorTransforms)
293 
294  # Now that we've collected all of the Transforms, we can finally build the
295  # (immutable) TransformMap.
296  transformMap = transformMapBuilder.build()
297 
298  # Second pass through the detectorConfigs: actually make Detector instances
299  detectorList = [Detector(transformMap=transformMap, **kw) for kw in detectorData]
300 
301  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:61
def makePixelToTanPixel(bbox, orientation, focalPlaneToField, pixelSizeMm)
def makeTransformDict(transformConfigDict)
def makeDetector(detectorConfig, ampInfoCatalog, focalPlaneToField)
An integer coordinate rectangle.
Definition: Box.h:54