LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
testUtils.py
Go to the documentation of this file.
1 import os
2 
3 import numpy
4 
5 import lsst.utils
6 import lsst.afw.geom as afwGeom
7 import lsst.afw.table as afwTable
8 from .cameraGeomLib import PIXELS, TAN_PIXELS, PUPIL, FOCAL_PLANE, SCIENCE, ACTUAL_PIXELS, \
9  CameraSys, Detector, Orientation
10 from .cameraConfig import DetectorConfig, CameraConfig
11 from .cameraFactory import makeCameraFromCatalogs
12 from .makePixelToTanPixel import makePixelToTanPixel
13 
14 __all__ = ["DetectorWrapper", "CameraWrapper"]
15 
16 
17 class DetectorWrapper(object):
18  """!A Detector and the data used to construct it
19 
20  Intended for use with unit tests, thus saves a copy of all input parameters.
21  Does not support setting details of amplifiers.
22  """
23  def __init__(self,
24  name="detector 1",
25  id=1,
26  detType=SCIENCE,
27  serial="xkcd722",
28  bbox=None, # do not use mutable objects as defaults
29  numAmps=3,
30  pixelSize=(0.02, 0.02),
31  ampExtent=(5, 6),
32  orientation=Orientation(),
33  plateScale=20.0,
34  radialDistortion=0.925,
35  modFunc=None,
36  ):
37  """!Construct a DetectorWrapper
38 
39  @param[in] name detector name
40  @param[in] id detector ID (int)
41  @param[in] detType detector type (an lsst.afw.cameraGeom.DetectorType)
42  @param[in] serial serial "number" (a string)
43  @param[in] bbox bounding box; defaults to (0, 0), (1024x1024) (an lsst.afw.geom.Box2I)
44  @param[in] numAmps number of amplifiers (int)
45  @param[in] pixelSize pixel size (mm) (an lsst.afw.geom.Point2D)
46  @param[in] ampExtent dimensions of amplifier image bbox (an lsst.afw.geom.Extent2I)
47  @param[in] orientation orientation of CCC in focal plane (lsst.afw.cameraGeom.Orientation)
48  @param[in] plateScale plate scale in arcsec/mm; 20.0 is for LSST
49  @param[in] radialDistortion radial distortion, in mm/rad^2
50  (the r^3 coefficient of the radial distortion polynomial
51  that converts PUPIL in radians to FOCAL_PLANE in mm);
52  0.925 is the value Dave Monet measured for lsstSim data
53  @param[in] modFunc a function that can modify attributes just before constructing the detector;
54  modFunc receives one argument: a DetectorWrapper with all attributes except detector set.
55  """
56  # note that (0., 0.) for the reference position is the center of the first pixel
57  self.name = name
58  self.id = int(id)
59  self.type = detType
60  self.serial = serial
61  if bbox is None:
62  bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1024, 1048))
63  self.bbox = bbox
64  self.pixelSize = afwGeom.Extent2D(*pixelSize)
65  self.ampExtent = afwGeom.Extent2I(*ampExtent)
66  self.plateScale = float(plateScale)
67  self.radialDistortion = float(radialDistortion)
68  schema = afwTable.AmpInfoTable.makeMinimalSchema()
70  for i in range(numAmps):
71  record = self.ampInfo.addNew()
72  ampName = "amp %d" % (i + 1,)
73  record.setName(ampName)
74  record.setBBox(afwGeom.Box2I(afwGeom.Point2I(-1, 1), self.ampExtent))
75  record.setGain(1.71234e3)
76  record.setReadNoise(0.521237e2)
77  record.setReadoutCorner(afwTable.LL)
78  record.setHasRawInfo(False)
79  self.orientation = orientation
80 
81  # compute TAN_PIXELS transform
82  pScaleRad = afwGeom.arcsecToRad(self.plateScale)
83  radialDistortCoeffs = [0.0, 1.0/pScaleRad, 0.0, self.radialDistortion/pScaleRad]
84  focalPlaneToPupil = afwGeom.RadialXYTransform(radialDistortCoeffs)
85  pixelToTanPixel = makePixelToTanPixel(
86  bbox = self.bbox,
87  orientation = self.orientation,
88  focalPlaneToPupil = focalPlaneToPupil,
89  pixelSizeMm = self.pixelSize,
90  plateScale = self.plateScale,
91  )
92 
93  self.transMap = {
94  FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize),
95  CameraSys(TAN_PIXELS, self.name): pixelToTanPixel,
96  CameraSys(ACTUAL_PIXELS, self.name): afwGeom.RadialXYTransform([0, 0.95, 0.01]),
97  }
98  if modFunc:
99  modFunc(self)
101  self.name,
102  self.id,
103  self.type,
104  self.serial,
105  self.bbox,
106  self.ampInfo,
107  self.orientation,
108  self.pixelSize,
109  self.transMap,
110  )
111 
112 class CameraWrapper(object):
113  """A simple Camera and the data used to construct it
114 
115  Intended for use with unit tests, thus saves some interesting information.
116  """
117  def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False):
118  """!Construct a CameraWrapper
119 
120  @param[in] plateScale plate scale in arcsec/mm; 20.0 is for LSST
121  @param[in] radialDistortion radial distortion, in mm/rad^2
122  (the r^3 coefficient of the radial distortion polynomial
123  that converts PUPIL in radians to FOCAL_PLANE in mm);
124  0.925 is the value Dave Monet measured for lsstSim data
125  @param[in] isLsstLike make repository products with one raw image per amplifier (True)
126  or with one raw image per detector (False)
127  """
128  afwDir = lsst.utils.getPackageDir("afw")
129  self._afwTestDir = os.path.join(afwDir, "tests")
130 
131  # Info to store for unit tests
132  self.plateScale = float(plateScale)
133  self.radialDistortion = float(radialDistortion)
135  self.detectorIdList = []
136  self.ampInfoDict = {}
137 
138  self.camConfig, self.ampCatalogDict = self.makeTestRepositoryItems(isLsstLike)
139  self.camera = makeCameraFromCatalogs(self.camConfig, self.ampCatalogDict)
140 
141  @property
142  def nDetectors(self):
143  """!Return the number of detectors"""
144  return len(self.detectorNameList)
145 
146  def makeDetectorConfigs(self, detFile):
147  """!Construct a list of DetectorConfig, one per detector
148  """
149  detectors = []
150  self.detectorNameList = []
151  self.detectorIdList = []
152  with open(detFile) as fh:
153  names = fh.readline().rstrip().lstrip("#").split("|")
154  for l in fh:
155  els = l.rstrip().split("|")
156  detectorProps = dict([(name, el) for name, el in zip(names, els)])
157  detectors.append(detectorProps)
158  detectorConfigs = []
159  for i, detector in enumerate(detectors):
160  detectorId = (i + 1) * 10 # to avoid simple 0, 1, 2...
161  detectorName = detector['name']
162  detConfig = DetectorConfig()
163  detConfig.name = detectorName
164  detConfig.id = detectorId
165  detConfig.bbox_x0 = 0
166  detConfig.bbox_y0 = 0
167  detConfig.bbox_x1 = int(detector['npix_x']) - 1
168  detConfig.bbox_y1 = int(detector['npix_y']) - 1
169  detConfig.serial = str(detector['serial'])
170  detConfig.detectorType = int(detector['detectorType'])
171  detConfig.offset_x = float(detector['x'])
172  detConfig.offset_y = float(detector['y'])
173  detConfig.refpos_x = float(detector['refPixPos_x'])
174  detConfig.refpos_y = float(detector['refPixPos_y'])
175  detConfig.yawDeg = float(detector['yaw'])
176  detConfig.pitchDeg = float(detector['pitch'])
177  detConfig.rollDeg = float(detector['roll'])
178  detConfig.pixelSize_x = float(detector['pixelSize'])
179  detConfig.pixelSize_y = float(detector['pixelSize'])
180  detConfig.transposeDetector = False
181  detConfig.transformDict.nativeSys = PIXELS.getSysName()
182  detectorConfigs.append(detConfig)
183  self.detectorNameList.append(detectorName)
184  self.detectorIdList.append(detectorId)
185  return detectorConfigs
186 
187  def makeAmpCatalogs(self, ampFile, isLsstLike=False):
188  """!Construct a list of AmpInfoCatalog, one per detector
189 
190  @param[in] ampFile path to amplifier data file
191  @param[in] isLsstLike if True then there is one raw image per amplifier;
192  if False then there is one raw image per detector
193  """
194  readoutMap = {'LL':0, 'LR':1, 'UR':2, 'UL':3}
195  amps = []
196  with open(ampFile) as fh:
197  names = fh.readline().rstrip().lstrip("#").split("|")
198  for l in fh:
199  els = l.rstrip().split("|")
200  ampProps = dict([(name, el) for name, el in zip(names, els)])
201  amps.append(ampProps)
202  ampTablesDict = {}
203  schema = afwTable.AmpInfoTable.makeMinimalSchema()
204  linThreshKey = schema.addField('linearityThreshold', type=float)
205  linMaxKey = schema.addField('linearityMaximum', type=float)
206  linUnitsKey = schema.addField('linearityUnits', type=str, size=9)
207  self.ampInfoDict = {}
208  for amp in amps:
209  if amp['ccd_name'] in ampTablesDict:
210  ampCatalog = ampTablesDict[amp['ccd_name']]
211  self.ampInfoDict[amp['ccd_name']]['namps'] += 1
212  else:
213  ampCatalog = afwTable.AmpInfoCatalog(schema)
214  ampTablesDict[amp['ccd_name']] = ampCatalog
215  self.ampInfoDict[amp['ccd_name']] = {'namps':1, 'linInfo':{}}
216  record = ampCatalog.addNew()
217  bbox = afwGeom.Box2I(afwGeom.Point2I(int(amp['trimmed_xmin']), int(amp['trimmed_ymin'])),
218  afwGeom.Point2I(int(amp['trimmed_xmax']), int(amp['trimmed_ymax'])))
219  rawBbox = afwGeom.Box2I(afwGeom.Point2I(int(amp['raw_xmin']), int(amp['raw_ymin'])),
220  afwGeom.Point2I(int(amp['raw_xmax']), int(amp['raw_ymax'])))
221  rawDataBbox = afwGeom.Box2I(afwGeom.Point2I(int(amp['raw_data_xmin']), int(amp['raw_data_ymin'])),
222  afwGeom.Point2I(int(amp['raw_data_xmax']), int(amp['raw_data_ymax'])))
223  rawHOverscanBbox = afwGeom.Box2I(afwGeom.Point2I(int(amp['hoscan_xmin']), int(amp['hoscan_ymin'])),
224  afwGeom.Point2I(int(amp['hoscan_xmax']), int(amp['hoscan_ymax'])))
225  rawVOverscanBbox = afwGeom.Box2I(afwGeom.Point2I(int(amp['voscan_xmin']), int(amp['voscan_ymin'])),
226  afwGeom.Point2I(int(amp['voscan_xmax']), int(amp['voscan_ymax'])))
227  rawPrescanBbox = afwGeom.Box2I(afwGeom.Point2I(int(amp['pscan_xmin']), int(amp['pscan_ymin'])),
228  afwGeom.Point2I(int(amp['pscan_xmax']), int(amp['pscan_ymax'])))
229  xoffset = int(amp['x_offset'])
230  yoffset = int(amp['y_offset'])
231  flipx = bool(int(amp['flipx']))
232  flipy = bool(int(amp['flipy']))
233  readcorner = 'LL'
234  if not isLsstLike:
235  offext = afwGeom.Extent2I(xoffset, yoffset)
236  if flipx:
237  xExt = rawBbox.getDimensions().getX()
238  rawBbox.flipLR(xExt)
239  rawDataBbox.flipLR(xExt)
240  rawHOverscanBbox.flipLR(xExt)
241  rawVOverscanBbox.flipLR(xExt)
242  rawPrescanBbox.flipLR(xExt)
243  if flipy:
244  yExt = rawBbox.getDimensions().getY()
245  rawBbox.flipTB(yExt)
246  rawDataBbox.flipTB(yExt)
247  rawHOverscanBbox.flipTB(yExt)
248  rawVOverscanBbox.flipTB(yExt)
249  rawPrescanBbox.flipTB(yExt)
250  if not flipx and not flipy:
251  readcorner = 'LL'
252  elif flipx and not flipy:
253  readcorner = 'LR'
254  elif flipx and flipy:
255  readcorner = 'UR'
256  elif not flipx and flipy:
257  readcorner = 'UL'
258  else:
259  raise RuntimeError("Couldn't find read corner")
260 
261  flipx = False
262  flipy = False
263  rawBbox.shift(offext)
264  rawDataBbox.shift(offext)
265  rawHOverscanBbox.shift(offext)
266  rawVOverscanBbox.shift(offext)
267  rawPrescanBbox.shift(offext)
268  xoffset = 0
269  yoffset = 0
270  offset = afwGeom.Extent2I(xoffset, yoffset)
271  record.setBBox(bbox)
272  record.setRawXYOffset(offset)
273  record.setName(str(amp['name']))
274  record.setReadoutCorner(readoutMap[readcorner])
275  record.setGain(float(amp['gain']))
276  record.setReadNoise(float(amp['readnoise']))
277  record.setLinearityCoeffs([float(amp['lin_coeffs']),])
278  record.setLinearityType(str(amp['lin_type']))
279  record.setHasRawInfo(True)
280  record.setRawFlipX(flipx)
281  record.setRawFlipY(flipy)
282  record.setRawBBox(rawBbox)
283  record.setRawDataBBox(rawDataBbox)
284  record.setRawHorizontalOverscanBBox(rawHOverscanBbox)
285  record.setRawVerticalOverscanBBox(rawVOverscanBbox)
286  record.setRawPrescanBBox(rawPrescanBbox)
287  record.set(linThreshKey, float(amp['lin_thresh']))
288  record.set(linMaxKey, float(amp['lin_max']))
289  record.set(linUnitsKey, str(amp['lin_units']))
290  #The current schema assumes third order coefficients
291  saveCoeffs = (float(amp['lin_coeffs']),)
292  saveCoeffs += (numpy.nan, numpy.nan, numpy.nan)
293  self.ampInfoDict[amp['ccd_name']]['linInfo'][amp['name']] = \
294  {'lincoeffs':saveCoeffs, 'lintype':str(amp['lin_type']),
295  'linthresh':float(amp['lin_thresh']), 'linmax':float(amp['lin_max']),
296  'linunits':str(amp['lin_units'])}
297  return ampTablesDict
298 
299  def makeTestRepositoryItems(self, isLsstLike=False):
300  """!Make camera config and amp catalog dictionary, using default detector and amp files
301 
302  @param[in] isLsstLike if True then there is one raw image per amplifier;
303  if False then there is one raw image per detector
304  """
305  detFile = os.path.join(self._afwTestDir, "testCameraDetectors.dat")
306  detectorConfigs = self.makeDetectorConfigs(detFile)
307  ampFile = os.path.join(self._afwTestDir, "testCameraAmps.dat")
308  ampCatalogDict = self.makeAmpCatalogs(ampFile, isLsstLike=isLsstLike)
309  camConfig = CameraConfig()
310  camConfig.name = "testCamera%s"%('LSST' if isLsstLike else 'SC')
311  camConfig.detectorList = dict((i, detConfig) for i, detConfig in enumerate(detectorConfigs))
312  camConfig.plateScale = self.plateScale
313  pScaleRad = afwGeom.arcsecToRad(self.plateScale)
314  radialDistortCoeffs = [0.0, 1.0/pScaleRad, 0.0, self.radialDistortion/pScaleRad]
315  tConfig = afwGeom.TransformConfig()
316  tConfig.transform.name = 'inverted'
317  radialClass = afwGeom.xyTransformRegistry['radial']
318  tConfig.transform.active.transform.retarget(radialClass)
319  tConfig.transform.active.transform.coeffs = radialDistortCoeffs
320  tmc = afwGeom.TransformMapConfig()
321  tmc.nativeSys = FOCAL_PLANE.getSysName()
322  tmc.transforms = {PUPIL.getSysName():tConfig}
323  camConfig.transformDict = tmc
324  return camConfig, ampCatalogDict
def __init__
Construct a CameraWrapper.
Definition: testUtils.py:117
def makeTestRepositoryItems
Make camera config and amp catalog dictionary, using default detector and amp files.
Definition: testUtils.py:299
A custom container class for records, based on std::vector.
Definition: Catalog.h:94
def nDetectors
Return the number of detectors.
Definition: testUtils.py:142
A Detector and the data used to construct it.
Definition: testUtils.py:17
def __init__
Construct a DetectorWrapper.
Definition: testUtils.py:36
An integer coordinate rectangle.
Definition: Box.h:53
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package
Definition: Utils.cc:66
def makePixelToTanPixel
Make an XYTransform whose forward direction converts PIXEL to TAN_PIXEL for one detector.
def makeDetectorConfigs
Construct a list of DetectorConfig, one per detector.
Definition: testUtils.py:146
def makeAmpCatalogs
Construct a list of AmpInfoCatalog, one per detector.
Definition: testUtils.py:187
A purely radial polynomial distortion, up to 6th order.
Definition: XYTransform.h:186
def makeCameraFromCatalogs
Construct a Camera instance from a dictionary of detector name: AmpInfoCatalog.
double arcsecToRad(double x)
Definition: Angle.h:41