267 self.
rng = np.random.RandomState(self.config.rngSeed)
269 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
270 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
271 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
272 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
273 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
274 [1e-2, 0.0, 0.0, 2.2e-2, 0.0, 0.0, 0.0, 0.0],
275 [1e-2, 5e-3, 5e-4, 3e-3, 4e-2, 5e-3, 5e-3, 0.0]])
278 [4., 16., 26., 16., 4.],
279 [7., 26., 41., 26., 7.],
280 [4., 16., 26., 16., 4.],
281 [1., 4., 7., 4., 1.]]) / 273.0
310 """Generate simulated ISR data.
312 Currently, only the class defined crosstalk coefficient
313 matrix, brighter-fatter kernel, a constant unity transmission
314 curve, or a simple single-entry defect list can be generated.
319 Simulated ISR data product.
321 if sum(map(bool, [self.config.doBrighterFatter,
322 self.config.doDefects,
323 self.config.doTransmissionCurve,
324 self.config.doCrosstalkCoeffs])) != 1:
325 raise RuntimeError(
"Only one data product can be generated at a time.")
326 elif self.config.doBrighterFatter
is True:
328 elif self.config.doDefects
is True:
330 elif self.config.doTransmissionCurve
is True:
332 elif self.config.doCrosstalkCoeffs
is True:
381 """Generate a simulated ISR image.
385 exposure : `lsst.afw.image.Exposure` or `dict`
386 Simulated ISR image data.
390 This method currently constructs a "raw" data image by:
392 * Generating a simulated sky with noise
393 * Adding a single Gaussian "star"
394 * Adding the fringe signal
395 * Multiplying the frame by the simulated flat
396 * Adding dark current (and noise)
397 * Adding a bias offset (and noise)
398 * Adding an overscan gradient parallel to the pixel y-axis
399 * Simulating crosstalk by adding a scaled version of each
400 amplifier to each other amplifier.
402 The exposure with image data constructed this way is in one of
405 * A single image, with overscan and prescan regions retained
406 * A single image, with overscan and prescan regions trimmed
407 * A `dict`, containing the amplifer data indexed by the
410 The nonlinearity, CTE, and brighter fatter are currently not
413 Note that this method generates an image in the reverse
414 direction as the ISR processing, as the output image here has
415 had a series of instrument effects added to an idealized
420 for idx, amp
in enumerate(exposure.getDetector()):
422 if self.config.isTrimmed
is True:
425 bbox = amp.getRawDataBBox()
427 ampData = exposure.image[bbox]
429 if self.config.doAddSky
is True:
430 self.
amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
432 if self.config.doAddSource
is True:
433 for sourceAmp, sourceFlux, sourceX, sourceY
in zip(self.config.sourceAmp,
434 self.config.sourceFlux,
436 self.config.sourceY):
440 if self.config.doAddFringe
is True:
442 x0=np.array(self.config.fringeX0),
443 y0=np.array(self.config.fringeY0))
445 if self.config.doAddFlat
is True:
446 if ampData.getArray().sum() == 0.0:
448 u0 = exposure.getDimensions().getX()
449 v0 = exposure.getDimensions().getY()
452 if self.config.doAddDark
is True:
454 self.config.darkRate * self.config.darkTime / self.config.gain,
455 np.sqrt(self.config.darkRate
456 * self.config.darkTime / self.config.gain))
458 if self.config.doAddCrosstalk
is True:
460 for idxS, ampS
in enumerate(exposure.getDetector()):
461 for idxT, ampT
in enumerate(exposure.getDetector()):
462 ampDataT = exposure.image[ampT.getBBox()
463 if self.config.isTrimmed
else ampT.getRawDataBBox()]
464 outAmp = ctCalib.extractAmp(exposure.getImage(), ampS, ampT,
465 isTrimmed=self.config.isTrimmed)
468 for amp
in exposure.getDetector():
470 if self.config.isTrimmed
is True:
473 bbox = amp.getRawDataBBox()
475 ampData = exposure.image[bbox]
477 if self.config.doAddBias
is True:
479 self.config.readNoise / self.config.gain)
481 if self.config.doAddOverscan
is True:
482 oscanBBox = amp.getRawHorizontalOverscanBBox()
483 oscanData = exposure.image[oscanBBox]
485 self.config.readNoise / self.config.gain)
488 1.0 * self.config.overscanScale)
490 1.0 * self.config.overscanScale)
492 if self.config.doGenerateAmpDict
is True:
494 for amp
in exposure.getDetector():
495 expDict[amp.getName()] = exposure
518 """Construct a test exposure.
520 The test exposure has a simple WCS set, as well as a list of
521 unlikely header keywords that can be removed during ISR
522 processing to exercise that code.
526 exposure : `lsst.afw.exposure.Exposure`
527 Construct exposure containing masked image of the
531 detector = camera[self.config.detectorIndex]
532 image = afwUtils.makeImageFromCcd(detector,
533 isTrimmed=self.config.isTrimmed,
537 imageFactory=afwImage.ImageF)
539 var = afwImage.ImageF(image.getDimensions())
540 mask = afwImage.Mask(image.getDimensions())
543 maskedImage = afwImage.makeMaskedImage(image, mask, var)
544 exposure = afwImage.makeExposure(maskedImage)
545 exposure.setDetector(detector)
546 exposure.setWcs(self.
getWcs())
548 visitInfo = afwImage.VisitInfo(exposureTime=self.config.expTime, darkTime=self.config.darkTime)
549 exposure.getInfo().setVisitInfo(visitInfo)
551 metadata = exposure.getMetadata()
552 metadata.add(
"SHEEP", 7.3,
"number of sheep on farm")
553 metadata.add(
"MONKEYS", 155,
"monkeys per tree")
554 metadata.add(
"VAMPIRES", 4,
"How scary are vampires.")
556 ccd = exposure.getDetector()
557 newCcd = ccd.rebuild()
560 newAmp = amp.rebuild()
561 newAmp.setLinearityCoeffs((0., 1., 0., 0.))
562 newAmp.setLinearityType(
"Polynomial")
563 newAmp.setGain(self.config.gain)
564 newAmp.setSuspectLevel(25000.0)
565 newAmp.setSaturation(32000.0)
566 newCcd.append(newAmp)
567 exposure.setDetector(newCcd.finish())
569 exposure.image.array[:] = np.zeros(exposure.getImage().getDimensions()).transpose()
570 exposure.mask.array[:] = np.zeros(exposure.getMask().getDimensions()).transpose()
571 exposure.variance.array[:] = np.zeros(exposure.getVariance().getDimensions()).transpose()
642 """Add a y-axis linear gradient to an amplifier's image data.
644 This method operates in the amplifier coordinate frame.
648 ampData : `lsst.afw.image.ImageF`
649 Amplifier image to operate on.
651 Start value of the gradient (at y=0).
653 End value of the gradient (at y=ymax).
655 nPixY = ampData.getDimensions().getY()
656 ampArr = ampData.array
657 ampArr[:] = ampArr[:] + (np.interp(range(nPixY), (0, nPixY - 1), (start, end)).reshape(nPixY, 1)
658 + np.zeros(ampData.getDimensions()).transpose())
661 """Add a single Gaussian source to an amplifier.
663 This method operates in the amplifier coordinate frame.
667 ampData : `lsst.afw.image.ImageF`
668 Amplifier image to operate on.
670 Peak flux of the source to add.
672 X-coordinate of the source peak.
674 Y-coordinate of the source peak.
676 for x
in range(0, ampData.getDimensions().getX()):
677 for y
in range(0, ampData.getDimensions().getY()):
678 ampData.array[y][x] = (ampData.array[y][x]
679 + scale * np.exp(-0.5 * ((x - x0)**2 + (y - y0)**2) / 3.0**2))
704 """Add a fringe-like ripple pattern to an amplifier's image data.
708 amp : `~lsst.afw.ampInfo.AmpInfoRecord`
709 Amplifier to operate on. Needed for amp<->exp coordinate
711 ampData : `lsst.afw.image.ImageF`
712 Amplifier image to operate on.
713 scale : `numpy.array` or `float`
714 Peak intensity scaling for the ripple.
715 x0 : `numpy.array` or `float`, optional
717 y0 : `numpy.array` or `float`, optional
722 This uses an offset sinc function to generate a ripple
723 pattern. True fringes have much finer structure, but this
724 pattern should be visually identifiable. The (x, y)
725 coordinates are in the frame of the amplifier, and (u, v) in
726 the frame of the full trimmed image.
728 for x
in range(0, ampData.getDimensions().getX()):
729 for y
in range(0, ampData.getDimensions().getY()):
731 ampData.getArray()[y][x] = np.sum((ampData.getArray()[y][x]
732 + scale * np.sinc(((u - x0) / 50)**2
733 + ((v - y0) / 50)**2)))
736 """Multiply an amplifier's image data by a flat-like pattern.
740 amp : `lsst.afw.ampInfo.AmpInfoRecord`
741 Amplifier to operate on. Needed for amp<->exp coordinate
743 ampData : `lsst.afw.image.ImageF`
744 Amplifier image to operate on.
746 Fractional drop from center to edge of detector along x-axis.
748 Peak location in detector coordinates.
750 Peak location in detector coordinates.
754 This uses a 2-d Gaussian to simulate an illumination pattern
755 that falls off towards the edge of the detector. The (x, y)
756 coordinates are in the frame of the amplifier, and (u, v) in
757 the frame of the full trimmed image.
760 raise RuntimeError(
"Flat fractional drop cannot be greater than 1.0")
762 sigma = u0 / np.sqrt(-2.0 * np.log(fracDrop))
764 for x
in range(0, ampData.getDimensions().getX()):
765 for y
in range(0, ampData.getDimensions().getY()):
767 f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
768 ampData.array[y][x] = (ampData.array[y][x] * f)