22 __all__ = [
"DetectorWrapper",
"CameraWrapper"]
32 from ._cameraGeom
import CameraSys, PIXELS, TAN_PIXELS, FIELD_ANGLE, FOCAL_PLANE, ACTUAL_PIXELS, Orientation
33 from ._cameraGeom
import Amplifier, ReadoutCorner
34 from ._camera
import Camera
35 from ._cameraGeom
import DetectorType
36 from .cameraConfig
import DetectorConfig, CameraConfig
37 from ._cameraFactory
import makeCameraFromAmpLists
38 from ._makePixelToTanPixel
import makePixelToTanPixel
39 from ._transformConfig
import TransformMapConfig
43 """A Detector and the data used to construct it
45 Intended for use with unit tests, thus saves a copy of all input parameters.
46 Does not support setting details of amplifiers.
50 name : `str` (optional)
54 detType : `lsst.afw.cameraGeom.DetectorType` (optional)
56 serial : `str` (optional)
58 bbox : `lsst.geom.Box2I` (optional)
59 Bounding box; defaults to (0, 0), (1024x1024).
60 numAmps : `int` (optional)
62 pixelSize : `lsst.geom.Point2D` (optional)
64 ampExtent : `lsst.geom.Extent2I` (optional)
65 Dimensions of amplifier image bbox.
66 orientation : `lsst.afw.cameraGeom.Orientation` (optional)
67 Orientation of CCC in focal plane.
68 plateScale : `float` (optional)
69 Plate scale in arcsec/mm; 20.0 is for LSST.
70 radialDistortion : `float` (optional)
71 Radial distortion, in mm/rad^2.
72 The r^3 coefficient of the radial distortion polynomial
73 that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm;
74 0.925 is the value Dave Monet measured for lsstSim data
75 crosstalk : `iterable` (optional)
76 Crosstalk coefficient matrix. If None, then no crosstalk correction
78 modFunc : `callable` (optional)
79 A function that can modify attributes just before constructing the
80 detector; modFunc receives one argument: a DetectorWrapper with all
81 attributes except detector set.
82 physicalType : `str` (optional)
83 The physical type of the device, e.g. CCD, E2V, HgCdTe
89 detType=DetectorType.SCIENCE,
93 pixelSize=(0.02, 0.02),
95 orientation=Orientation(),
97 radialDistortion=0.925,
120 radialDistortCoeffs = [0.0, 1.0/pScaleRad,
126 focalPlaneToField=focalPlaneToField,
129 tanPixelSys = CameraSys(TAN_PIXELS, self.
namename)
130 actualPixelSys = CameraSys(ACTUAL_PIXELS, self.
namename)
133 tanPixelSys: pixelToTanPixel,
136 if crosstalk
is None:
137 crosstalk = [[0.0
for _
in range(numAmps)]
for _
in range(numAmps)]
140 if cameraBuilder
is None:
143 for i
in range(numAmps):
145 ampName = f
"amp {i + 1}"
146 ampBuilder.setName(ampName)
148 ampBuilder.setGain(1.71234e3)
149 ampBuilder.setReadNoise(0.521237e2)
150 ampBuilder.setReadoutCorner(ReadoutCorner.LL)
154 detectorBuilder = cameraBuilder.add(self.
namename, self.
idid)
155 detectorBuilder.setType(self.
typetype)
156 detectorBuilder.setSerial(self.
serialserial)
157 detectorBuilder.setPhysicalType(self.
physicalTypephysicalType)
158 detectorBuilder.setBBox(self.
bboxbbox)
159 detectorBuilder.setOrientation(self.
orientationorientation)
160 detectorBuilder.setPixelSize(self.
pixelSizepixelSize)
161 detectorBuilder.setTransformFromPixelsTo(tanPixelSys, self.
transMaptransMap[tanPixelSys])
162 detectorBuilder.setTransformFromPixelsTo(actualPixelSys, self.
transMaptransMap[actualPixelSys])
163 detectorBuilder.setCrosstalk(np.array(self.
crosstalkcrosstalk, dtype=np.float32))
164 for ampBuilder
in self.
ampListampList:
165 detectorBuilder.append(ampBuilder)
166 camera = cameraBuilder.finish()
171 """A simple Camera and the data used to construct it
173 Intended for use with unit tests, thus saves some interesting information.
178 Plate scale in arcsec/mm; 20.0 is for LSST.
179 radialDistortion : `float`
180 Radial distortion, in mm/rad^2.
181 The r^3 coefficient of the radial distortion polynomial
182 that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm;
183 0.925 is the value Dave Monet measured for lsstSim data.
185 Make repository products with one raw image per amplifier (True)
186 or with one raw image per detector (False).
189 def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False):
191 self.
_afwTestDataDir_afwTestDataDir = os.path.join(afwDir,
"python",
"lsst",
"afw",
192 "cameraGeom",
"testData")
209 """Return the number of detectors"""
213 """Construct a list of DetectorConfig, one per detector
218 with open(detFile)
as fh:
219 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
221 els = line.rstrip().split(
"|")
222 detectorProps = dict([(name, el)
223 for name, el
in zip(names, els)])
224 detectors.append(detectorProps)
226 for i, detector
in enumerate(detectors):
227 detectorId = (i + 1) * 10
228 detectorName = detector[
'name']
230 detConfig.name = detectorName
231 detConfig.id = detectorId
232 detConfig.bbox_x0 = 0
233 detConfig.bbox_y0 = 0
234 detConfig.bbox_x1 = int(detector[
'npix_x']) - 1
235 detConfig.bbox_y1 = int(detector[
'npix_y']) - 1
236 detConfig.serial = str(detector[
'serial'])
237 detConfig.detectorType = int(detector[
'detectorType'])
238 detConfig.offset_x = float(detector[
'x'])
239 detConfig.offset_y = float(detector[
'y'])
240 detConfig.refpos_x = float(detector[
'refPixPos_x'])
241 detConfig.refpos_y = float(detector[
'refPixPos_y'])
242 detConfig.yawDeg = float(detector[
'yaw'])
243 detConfig.pitchDeg = float(detector[
'pitch'])
244 detConfig.rollDeg = float(detector[
'roll'])
245 detConfig.pixelSize_x = float(detector[
'pixelSize'])
246 detConfig.pixelSize_y = float(detector[
'pixelSize'])
247 detConfig.transposeDetector =
False
248 detConfig.transformDict.nativeSys = PIXELS.getSysName()
249 detectorConfigs.append(detConfig)
252 return detectorConfigs
255 """Construct a dict of list of Amplifer, one list per detector.
260 Path to amplifier data file.
262 If True then there is one raw image per amplifier;
263 if False then there is one raw image per detector.
266 'LL': ReadoutCorner.LL,
267 'LR': ReadoutCorner.LR,
268 'UR': ReadoutCorner.UR,
269 'UL': ReadoutCorner.UL,
272 with open(ampFile)
as fh:
273 names = fh.readline().rstrip().lstrip(
"#").split(
"|")
275 els = line.rstrip().split(
"|")
276 ampProps = dict([(name, el)
for name, el
in zip(names, els)])
277 ampDataList.append(ampProps)
280 for ampData
in ampDataList:
281 if ampData[
'ccd_name']
in ampListDict:
282 ampList = ampListDict[ampData[
'ccd_name']]
283 self.
ampDataDictampDataDict[ampData[
'ccd_name']][
'namps'] += 1
286 ampListDict[ampData[
'ccd_name']] = ampList
287 self.
ampDataDictampDataDict[ampData[
'ccd_name']] = {
'namps': 1,
'linInfo': {}}
290 int(ampData[
'trimmed_ymin'])),
292 int(ampData[
'trimmed_ymax'])))
294 int(ampData[
'raw_ymin'])),
296 int(ampData[
'raw_ymax'])))
299 int(ampData[
'raw_data_ymin'])),
301 int(ampData[
'raw_data_ymax'])))
304 int(ampData[
'hoscan_ymin'])),
306 int(ampData[
'hoscan_ymax'])))
309 int(ampData[
'voscan_ymin'])),
311 int(ampData[
'voscan_ymax'])))
314 int(ampData[
'pscan_ymin'])),
316 int(ampData[
'pscan_ymax'])))
317 xoffset = int(ampData[
'x_offset'])
318 yoffset = int(ampData[
'y_offset'])
319 flipx = bool(int(ampData[
'flipx']))
320 flipy = bool(int(ampData[
'flipy']))
325 xExt = rawBbox.getDimensions().getX()
327 rawDataBbox.flipLR(xExt)
328 rawHOverscanBbox.flipLR(xExt)
329 rawVOverscanBbox.flipLR(xExt)
330 rawPrescanBbox.flipLR(xExt)
332 yExt = rawBbox.getDimensions().getY()
334 rawDataBbox.flipTB(yExt)
335 rawHOverscanBbox.flipTB(yExt)
336 rawVOverscanBbox.flipTB(yExt)
337 rawPrescanBbox.flipTB(yExt)
338 if not flipx
and not flipy:
340 elif flipx
and not flipy:
342 elif flipx
and flipy:
344 elif not flipx
and flipy:
347 raise RuntimeError(
"Couldn't find read corner")
351 rawBbox.shift(offext)
352 rawDataBbox.shift(offext)
353 rawHOverscanBbox.shift(offext)
354 rawVOverscanBbox.shift(offext)
355 rawPrescanBbox.shift(offext)
359 builder.setBBox(bbox)
360 builder.setRawXYOffset(offset)
361 builder.setName(str(ampData[
'name']))
362 builder.setReadoutCorner(readoutMap[readcorner])
363 builder.setGain(float(ampData[
'gain']))
364 builder.setReadNoise(float(ampData[
'readnoise']))
365 linCoeffs = np.array([float(ampData[
'lin_coeffs']), ], dtype=float)
366 builder.setLinearityCoeffs(linCoeffs)
367 builder.setLinearityType(str(ampData[
'lin_type']))
368 builder.setRawFlipX(flipx)
369 builder.setRawFlipY(flipy)
370 builder.setRawBBox(rawBbox)
371 builder.setRawDataBBox(rawDataBbox)
372 builder.setRawHorizontalOverscanBBox(rawHOverscanBbox)
373 builder.setRawVerticalOverscanBBox(rawVOverscanBbox)
374 builder.setRawPrescanBBox(rawPrescanBbox)
375 builder.setLinearityThreshold(float(ampData[
'lin_thresh']))
376 builder.setLinearityMaximum(float(ampData[
'lin_max']))
377 builder.setLinearityUnits(str(ampData[
'lin_units']))
378 self.
ampDataDictampDataDict[ampData[
'ccd_name']][
'linInfo'][ampData[
'name']] = \
379 {
'lincoeffs': linCoeffs,
'lintype': str(ampData[
'lin_type']),
380 'linthresh': float(ampData[
'lin_thresh']),
'linmax': float(ampData[
'lin_max']),
381 'linunits': str(ampData[
'lin_units'])}
382 ampList.append(builder)
386 """Make camera config and amp catalog dictionary, using default
387 detector and amp files.
392 If True then there is one raw image per amplifier;
393 if False then there is one raw image per detector.
395 detFile = os.path.join(self.
_afwTestDataDir_afwTestDataDir,
"testCameraDetectors.dat")
397 ampFile = os.path.join(self.
_afwTestDataDir_afwTestDataDir,
"testCameraAmps.dat")
398 ampListDict = self.
makeAmpListsmakeAmpLists(ampFile, isLsstLike=isLsstLike)
400 camConfig.name =
"testCamera%s"%(
'LSST' if isLsstLike
else 'SC')
401 camConfig.detectorList = dict((i, detConfig)
402 for i, detConfig
in enumerate(detectorConfigs))
403 camConfig.plateScale = self.
plateScaleplateScale
405 radialDistortCoeffs = [0.0, 1.0/pScaleRad,
408 tConfig.transform.name =
'inverted'
409 radialClass = afwGeom.transformRegistry[
'radial']
410 tConfig.transform.active.transform.retarget(radialClass)
411 tConfig.transform.active.transform.coeffs = radialDistortCoeffs
413 tmc.nativeSys = FOCAL_PLANE.getSysName()
414 tmc.transforms = {FIELD_ANGLE.getSysName(): tConfig}
415 camConfig.transformDict = tmc
416 return camConfig, ampListDict
421 """Compare two Point2D(Point2D) functions by evaluating them over a
426 dVal = (maxVal - minVal) / (nVal - 1)
427 for xInd
in range(nVal):
428 x = minVal + (xInd * dVal)
429 for yInd
in range(nVal):
430 y = minVal + (yInd * dVal)
432 res1 = func1(fromPoint)
433 res2 = func2(fromPoint)
434 self.assertPairsAlmostEqual(res1, res2)
439 """Compare two TransformMaps.
441 self.assertEqual(
list(map1),
list(map2))
444 with self.subTest(sysFrom=sysFrom, sysTo=sysTo):
445 transform1 = map1.getTransform(sysFrom, sysTo)
446 transform2 = map2.getTransform(sysFrom, sysTo)
447 self.compare2DFunctions(transform1.applyForward, transform2.applyForward, **kwds)
448 self.compare2DFunctions(transform1.applyInverse, transform2.applyInverse, **kwds)
453 self.assertEqual(amp1.getName(), amp2.getName())
454 self.assertEqual(amp1.getBBox(), amp2.getBBox())
455 self.assertFloatsEqual(amp1.getGain(), amp2.getGain(), ignoreNaNs=
True)
456 self.assertFloatsEqual(amp1.getReadNoise(), amp2.getReadNoise(), ignoreNaNs=
True)
457 self.assertFloatsEqual(amp1.getSaturation(), amp2.getSaturation(), ignoreNaNs=
True)
458 self.assertEqual(amp1.getReadoutCorner(), amp2.getReadoutCorner())
459 self.assertFloatsEqual(amp1.getSuspectLevel(), amp2.getSuspectLevel(), ignoreNaNs=
True)
460 self.assertEqual(amp1.getLinearityCoeffs().shape, amp2.getLinearityCoeffs().shape)
461 self.assertFloatsEqual(amp1.getLinearityCoeffs(), amp2.getLinearityCoeffs(), ignoreNaNs=
True)
462 self.assertEqual(amp1.getLinearityType(), amp2.getLinearityType())
463 self.assertFloatsEqual(amp1.getLinearityThreshold(), amp2.getLinearityThreshold(), ignoreNaNs=
True)
464 self.assertFloatsEqual(amp1.getLinearityMaximum(), amp2.getLinearityMaximum(), ignoreNaNs=
True)
465 self.assertEqual(amp1.getLinearityUnits(), amp2.getLinearityUnits())
466 self.assertEqual(amp1.getRawBBox(), amp2.getRawBBox())
467 self.assertEqual(amp1.getRawDataBBox(), amp2.getRawDataBBox())
468 self.assertEqual(amp1.getRawFlipX(), amp2.getRawFlipX())
469 self.assertEqual(amp1.getRawFlipY(), amp2.getRawFlipY())
470 self.assertEqual(amp1.getRawHorizontalOverscanBBox(), amp2.getRawHorizontalOverscanBBox())
471 self.assertEqual(amp1.getRawVerticalOverscanBBox(), amp2.getRawVerticalOverscanBBox())
472 self.assertEqual(amp1.getRawPrescanBBox(), amp2.getRawPrescanBBox())
477 """Compare two Detectors.
479 self.assertEqual(detector1.getName(), detector2.getName())
480 self.assertEqual(detector1.getId(), detector2.getId())
481 self.assertEqual(detector1.getSerial(), detector2.getSerial())
482 self.assertEqual(detector1.getPhysicalType(), detector2.getPhysicalType())
483 self.assertEqual(detector1.getBBox(), detector2.getBBox())
484 self.assertEqual(detector1.getPixelSize(), detector2.getPixelSize())
485 orientationIn = detector1.getOrientation()
486 orientationOut = detector2.getOrientation()
487 self.assertEqual(orientationIn.getFpPosition(), orientationOut.getFpPosition())
488 self.assertEqual(orientationIn.getReferencePoint(), orientationOut.getReferencePoint())
489 self.assertEqual(orientationIn.getYaw(), orientationOut.getYaw())
490 self.assertEqual(orientationIn.getPitch(), orientationOut.getPitch())
491 self.assertEqual(orientationIn.getRoll(), orientationOut.getRoll())
492 self.assertFloatsEqual(detector1.getCrosstalk(), detector2.getCrosstalk())
493 if compareTransforms:
494 self.assertTransformMapsEqual(detector1.getTransformMap(), detector2.getTransformMap(), **kwds)
495 self.assertEqual(len(detector1.getAmplifiers()), len(detector2.getAmplifiers()))
496 for amp1, amp2
in zip(detector1.getAmplifiers(), detector2.getAmplifiers()):
497 self.assertAmplifiersEqual(amp1, amp2)
502 """Compare two DetectorCollections.
504 self.assertCountEqual(
list(collection1.getNameIter()),
list(collection2.getNameIter()))
505 for k
in collection1.getNameIter():
506 self.assertDetectorsEqual(collection1[k], collection2[k], **kwds)
511 """Compare two Camers.
513 self.assertDetectorCollectionsEqual(camera1, camera2, **kwds)
514 self.assertTransformMapsEqual(camera1.getTransformMap(), camera2.getTransformMap())
515 self.assertEqual(camera1.getName(), camera2.getName())
516 self.assertEqual(camera1.getPupilFactoryName(), camera2.getPupilFactoryName())
A mutable Amplifier subclass class that can be used to incrementally construct or modify Amplifiers.
A helper class for creating and modifying cameras.
def makeDetectorConfigs(self, detFile)
def makeTestRepositoryItems(self, isLsstLike=False)
def makeAmpLists(self, ampFile, isLsstLike=False)
def __init__(self, plateScale=20.0, radialDistortion=0.925, isLsstLike=False)
def __init__(self, name="detector 1", id=1, detType=DetectorType.SCIENCE, serial="xkcd722", bbox=None, numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, crosstalk=None, modFunc=None, physicalType="CCD", cameraBuilder=None)
An integer coordinate rectangle.
daf::base::PropertyList * list
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def makeCameraFromAmpLists(cameraConfig, ampListDict, pupilFactoryClass=PupilFactory)
def makePixelToTanPixel(bbox, orientation, focalPlaneToField, pixelSizeMm)
def assertAmplifiersEqual(self, amp1, amp2)
def compare2DFunctions(self, func1, func2, minVal=-10, maxVal=None, nVal=5)
def assertTransformMapsEqual(self, map1, map2, **kwds)
def assertCamerasEqual(self, camera1, camera2, **kwds)
def assertDetectorCollectionsEqual(self, collection1, collection2, **kwds)
def assertDetectorsEqual(self, detector1, detector2, *compareTransforms=True, **kwds)
std::shared_ptr< TransformPoint2ToPoint2 > makeRadialTransform(std::vector< double > const &coeffs)
A purely radial polynomial distortion.
constexpr double arcsecToRad(double x) noexcept
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package