268 self.
rng = np.random.RandomState(self.config.rngSeed)
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, 0.0, 0.0, 0.0, 0.0, 0.0],
275 [1e-2, 0.0, 0.0, 2.2e-2, 0.0, 0.0, 0.0, 0.0],
276 [1e-2, 5e-3, 5e-4, 3e-3, 4e-2, 5e-3, 5e-3, 0.0]])
279 [4., 16., 26., 16., 4.],
280 [7., 26., 41., 26., 7.],
281 [4., 16., 26., 16., 4.],
282 [1., 4., 7., 4., 1.]]) / 273.0
311 """Generate simulated ISR data.
313 Currently, only the class defined crosstalk coefficient
314 matrix, brighter-fatter kernel, a constant unity transmission
315 curve, or a simple single-entry defect list can be generated.
320 Simulated ISR data product.
322 if sum(map(bool, [self.config.doBrighterFatter,
323 self.config.doDefects,
324 self.config.doTransmissionCurve,
325 self.config.doCrosstalkCoeffs])) != 1:
326 raise RuntimeError(
"Only one data product can be generated at a time.")
327 elif self.config.doBrighterFatter
is True:
329 elif self.config.doDefects
is True:
331 elif self.config.doTransmissionCurve
is True:
333 elif self.config.doCrosstalkCoeffs
is True:
382 """Generate a simulated ISR image.
386 exposure : `lsst.afw.image.Exposure` or `dict`
387 Simulated ISR image data.
391 This method currently constructs a "raw" data image by:
393 * Generating a simulated sky with noise
394 * Adding a single Gaussian "star"
395 * Adding the fringe signal
396 * Multiplying the frame by the simulated flat
397 * Adding dark current (and noise)
398 * Adding a bias offset (and noise)
399 * Adding an overscan gradient parallel to the pixel y-axis
400 * Simulating crosstalk by adding a scaled version of each
401 amplifier to each other amplifier.
403 The exposure with image data constructed this way is in one of
406 * A single image, with overscan and prescan regions retained
407 * A single image, with overscan and prescan regions trimmed
408 * A `dict`, containing the amplifer data indexed by the
411 The nonlinearity, CTE, and brighter fatter are currently not
414 Note that this method generates an image in the reverse
415 direction as the ISR processing, as the output image here has
416 had a series of instrument effects added to an idealized
421 for idx, amp
in enumerate(exposure.getDetector()):
423 if self.config.isTrimmed
is True:
426 bbox = amp.getRawDataBBox()
428 ampData = exposure.image[bbox]
430 if self.config.doAddSky
is True:
431 self.
amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
433 if self.config.doAddSource
is True:
434 for sourceAmp, sourceFlux, sourceX, sourceY
in zip(self.config.sourceAmp,
435 self.config.sourceFlux,
437 self.config.sourceY):
441 if self.config.doAddFringe
is True:
443 x0=np.array(self.config.fringeX0),
444 y0=np.array(self.config.fringeY0))
446 if self.config.doAddFlat
is True:
447 if ampData.getArray().sum() == 0.0:
449 u0 = exposure.getDimensions().getX()
450 v0 = exposure.getDimensions().getY()
453 if self.config.doAddDark
is True:
455 self.config.darkRate * self.config.darkTime / self.config.gain,
456 np.sqrt(self.config.darkRate
457 * self.config.darkTime / self.config.gain))
459 if self.config.doAddCrosstalk
is True:
461 for idxS, ampS
in enumerate(exposure.getDetector()):
462 for idxT, ampT
in enumerate(exposure.getDetector()):
463 ampDataT = exposure.image[ampT.getBBox()
464 if self.config.isTrimmed
else ampT.getRawDataBBox()]
465 outAmp = ctCalib.extractAmp(exposure.getImage(), ampS, ampT,
466 isTrimmed=self.config.isTrimmed)
469 for amp
in exposure.getDetector():
471 if self.config.isTrimmed
is True:
474 bbox = amp.getRawDataBBox()
476 ampData = exposure.image[bbox]
478 if self.config.doAddBias
is True:
480 self.config.readNoise / self.config.gain)
482 if self.config.doAddOverscan
is True:
483 oscanBBox = amp.getRawHorizontalOverscanBBox()
484 oscanData = exposure.image[oscanBBox]
486 self.config.readNoise / self.config.gain)
489 1.0 * self.config.overscanScale)
491 1.0 * self.config.overscanScale)
493 if self.config.doGenerateAmpDict
is True:
495 for amp
in exposure.getDetector():
496 expDict[amp.getName()] = exposure
519 """Construct a test exposure.
521 The test exposure has a simple WCS set, as well as a list of
522 unlikely header keywords that can be removed during ISR
523 processing to exercise that code.
527 exposure : `lsst.afw.exposure.Exposure`
528 Construct exposure containing masked image of the
532 detector = camera[self.config.detectorIndex]
533 image = afwUtils.makeImageFromCcd(detector,
534 isTrimmed=self.config.isTrimmed,
538 imageFactory=afwImage.ImageF)
540 var = afwImage.ImageF(image.getDimensions())
541 mask = afwImage.Mask(image.getDimensions())
544 maskedImage = afwImage.makeMaskedImage(image, mask, var)
545 exposure = afwImage.makeExposure(maskedImage)
546 exposure.setDetector(detector)
547 exposure.setWcs(self.
getWcs())
549 visitInfo = afwImage.VisitInfo(exposureTime=self.config.expTime, darkTime=self.config.darkTime)
550 exposure.getInfo().setVisitInfo(visitInfo)
552 metadata = exposure.getMetadata()
553 metadata.add(
"SHEEP", 7.3,
"number of sheep on farm")
554 metadata.add(
"MONKEYS", 155,
"monkeys per tree")
555 metadata.add(
"VAMPIRES", 4,
"How scary are vampires.")
557 ccd = exposure.getDetector()
558 newCcd = ccd.rebuild()
561 'LL': ReadoutCorner.LL,
562 'LR': ReadoutCorner.LR,
563 'UR': ReadoutCorner.UR,
564 'UL': ReadoutCorner.UL,
567 newAmp = amp.rebuild()
568 newAmp.setLinearityCoeffs((0., 1., 0., 0.))
569 newAmp.setLinearityType(
"Polynomial")
570 newAmp.setGain(self.config.gain)
571 newAmp.setSuspectLevel(25000.0)
572 newAmp.setSaturation(32000.0)
576 imageBBox = amp.getRawDataBBox()
577 rawBbox = amp.getRawBBox()
578 parallelOscanBBox = amp.getRawParallelOverscanBBox()
579 serialOscanBBox = amp.getRawSerialOverscanBBox()
580 prescanBBox = amp.getRawPrescanBBox()
582 flipx = bool(amp.getRawFlipX())
583 flipy = bool(amp.getRawFlipY())
585 xExt = rawBbox.getDimensions().getX()
587 imageBBox.flipLR(xExt)
588 parallelOscanBBox.flipLR(xExt)
589 serialOscanBBox.flipLR(xExt)
590 prescanBBox.flipLR(xExt)
592 yExt = rawBbox.getDimensions().getY()
594 imageBBox.flipTB(yExt)
595 parallelOscanBBox.flipTB(yExt)
596 serialOscanBBox.flipTB(yExt)
597 prescanBBox.flipTB(yExt)
598 if not flipx
and not flipy:
600 elif flipx
and not flipy:
602 elif flipx
and flipy:
604 elif not flipx
and flipy:
606 newAmp.setReadoutCorner(readoutMap[readoutCorner])
607 newAmp.setRawBBox(rawBbox)
608 newAmp.setRawDataBBox(imageBBox)
609 newAmp.setRawParallelOverscanBBox(parallelOscanBBox)
610 newAmp.setRawSerialOverscanBBox(serialOscanBBox)
611 newAmp.setRawPrescanBBox(prescanBBox)
612 newAmp.setRawFlipX(
False)
613 newAmp.setRawFlipY(
False)
615 newCcd.append(newAmp)
617 exposure.setDetector(newCcd.finish())
619 exposure.image.array[:] = np.zeros(exposure.getImage().getDimensions()).transpose()
620 exposure.mask.array[:] = np.zeros(exposure.getMask().getDimensions()).transpose()
621 exposure.variance.array[:] = np.zeros(exposure.getVariance().getDimensions()).transpose()
692 """Add a y-axis linear gradient to an amplifier's image data.
694 This method operates in the amplifier coordinate frame.
698 ampData : `lsst.afw.image.ImageF`
699 Amplifier image to operate on.
701 Start value of the gradient (at y=0).
703 End value of the gradient (at y=ymax).
705 nPixY = ampData.getDimensions().getY()
706 ampArr = ampData.array
707 ampArr[:] = ampArr[:] + (np.interp(range(nPixY), (0, nPixY - 1), (start, end)).reshape(nPixY, 1)
708 + np.zeros(ampData.getDimensions()).transpose())
711 """Add a single Gaussian source to an amplifier.
713 This method operates in the amplifier coordinate frame.
717 ampData : `lsst.afw.image.ImageF`
718 Amplifier image to operate on.
720 Peak flux of the source to add.
722 X-coordinate of the source peak.
724 Y-coordinate of the source peak.
726 for x
in range(0, ampData.getDimensions().getX()):
727 for y
in range(0, ampData.getDimensions().getY()):
728 ampData.array[y][x] = (ampData.array[y][x]
729 + scale * np.exp(-0.5 * ((x - x0)**2 + (y - y0)**2) / 3.0**2))
754 """Add a fringe-like ripple pattern to an amplifier's image data.
758 amp : `~lsst.afw.ampInfo.AmpInfoRecord`
759 Amplifier to operate on. Needed for amp<->exp coordinate
761 ampData : `lsst.afw.image.ImageF`
762 Amplifier image to operate on.
763 scale : `numpy.array` or `float`
764 Peak intensity scaling for the ripple.
765 x0 : `numpy.array` or `float`, optional
767 y0 : `numpy.array` or `float`, optional
772 This uses an offset sinc function to generate a ripple
773 pattern. True fringes have much finer structure, but this
774 pattern should be visually identifiable. The (x, y)
775 coordinates are in the frame of the amplifier, and (u, v) in
776 the frame of the full trimmed image.
778 for x
in range(0, ampData.getDimensions().getX()):
779 for y
in range(0, ampData.getDimensions().getY()):
781 ampData.getArray()[y][x] = np.sum((ampData.getArray()[y][x]
782 + scale * np.sinc(((u - x0) / 50)**2
783 + ((v - y0) / 50)**2)))
786 """Multiply an amplifier's image data by a flat-like pattern.
790 amp : `lsst.afw.ampInfo.AmpInfoRecord`
791 Amplifier to operate on. Needed for amp<->exp coordinate
793 ampData : `lsst.afw.image.ImageF`
794 Amplifier image to operate on.
796 Fractional drop from center to edge of detector along x-axis.
798 Peak location in detector coordinates.
800 Peak location in detector coordinates.
804 This uses a 2-d Gaussian to simulate an illumination pattern
805 that falls off towards the edge of the detector. The (x, y)
806 coordinates are in the frame of the amplifier, and (u, v) in
807 the frame of the full trimmed image.
810 raise RuntimeError(
"Flat fractional drop cannot be greater than 1.0")
812 sigma = u0 / np.sqrt(-2.0 * np.log(fracDrop))
814 for x
in range(0, ampData.getDimensions().getX()):
815 for y
in range(0, ampData.getDimensions().getY()):
817 f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
818 ampData.array[y][x] = (ampData.array[y][x] * f)