34 __all__ = [
"IsrMockConfig",
"IsrMock",
"RawMock",
"TrimmedRawMock",
"RawDictMock",
35 "CalibratedRawMock",
"MasterMock",
36 "BiasMock",
"DarkMock",
"FlatMock",
"FringeMock",
"UntrimmedFringeMock",
37 "BfKernelMock",
"DefectMock",
"CrosstalkCoeffMock",
"TransmissionMock",
42 r"""Configuration parameters for isrMock. 44 These parameters produce generic fixed position signals from 45 various sources, and combine them in a way that matches how those 46 signals are combined to create real data. The camera used is the 47 test camera defined by the afwUtils code. 50 isLsstLike = pexConfig.Field(
53 doc=
"If True, products have one raw image per amplifier, otherwise, one raw image per detector.",
55 isTrimmed = pexConfig.Field(
58 doc=
"If True, amplifiers have been trimmed and mosaicked to remove regions outside the data BBox.",
60 detectorIndex = pexConfig.Field(
63 doc=
"Index for the detector to use. The default value uses a standard 2x4 array of amps.",
65 rngSeed = pexConfig.Field(
68 doc=
"Seed for random number generator used to add noise.",
71 gain = pexConfig.Field(
74 doc=
"Gain for simulated data in e^-/DN.",
76 readNoise = pexConfig.Field(
79 doc=
"Read noise of the detector in e-.",
81 expTime = pexConfig.Field(
84 doc=
"Exposure time for simulated data.",
88 skyLevel = pexConfig.Field(
91 doc=
"Background contribution to be generated from 'the sky' in DN.",
93 sourceFlux = pexConfig.ListField(
96 doc=
"Peak flux level (in DN) of simulated 'astronomical sources'.",
98 sourceAmp = pexConfig.ListField(
101 doc=
"Amplifier to place simulated 'astronomical sources'.",
103 sourceX = pexConfig.ListField(
106 doc=
"Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
108 sourceY = pexConfig.ListField(
111 doc=
"Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
113 overscanScale = pexConfig.Field(
116 doc=
"Amplitude (in DN) of the ramp function to add to overscan data.",
118 biasLevel = pexConfig.Field(
121 doc=
"Background contribution to be generated from the bias offset in DN.",
123 darkRate = pexConfig.Field(
126 doc=
"Background level contribution (in e-/s) to be generated from dark current.",
128 darkTime = pexConfig.Field(
131 doc=
"Exposure time for the dark current contribution.",
133 flatDrop = pexConfig.Field(
136 doc=
"Fractional flux drop due to flat from center to edge of detector along x-axis.",
138 fringeScale = pexConfig.Field(
141 doc=
"Peak flux for the fringe ripple in DN.",
145 doAddSky = pexConfig.Field(
148 doc=
"Apply 'sky' signal to output image.",
150 doAddSource = pexConfig.Field(
153 doc=
"Add simulated source to output image.",
155 doAddCrosstalk = pexConfig.Field(
158 doc=
"Apply simulated crosstalk to output image. This cannot be corrected by ISR, " 159 "as detector.hasCrosstalk()==False.",
161 doAddOverscan = pexConfig.Field(
164 doc=
"If untrimmed, add overscan ramp to overscan and data regions.",
166 doAddBias = pexConfig.Field(
169 doc=
"Add bias signal to data.",
171 doAddDark = pexConfig.Field(
174 doc=
"Add dark signal to data.",
176 doAddFlat = pexConfig.Field(
179 doc=
"Add flat signal to data.",
181 doAddFringe = pexConfig.Field(
184 doc=
"Add fringe signal to data.",
188 doTransmissionCurve = pexConfig.Field(
191 doc=
"Return a simulated transmission curve.",
193 doDefects = pexConfig.Field(
196 doc=
"Return a simulated defect list.",
198 doBrighterFatter = pexConfig.Field(
201 doc=
"Return a simulated brighter-fatter kernel.",
203 doCrosstalkCoeffs = pexConfig.Field(
206 doc=
"Return the matrix of crosstalk coefficients.",
208 doDataRef = pexConfig.Field(
211 doc=
"Return a simulated gen2 butler dataRef.",
213 doGenerateImage = pexConfig.Field(
216 doc=
"Return the generated output image if True.",
218 doGenerateData = pexConfig.Field(
221 doc=
"Return a non-image data structure if True.",
223 doGenerateAmpDict = pexConfig.Field(
226 doc=
"Return a dict of exposure amplifiers instead of an afwImage.Exposure.",
231 r"""Class to generate consistent mock images for ISR testing. 233 ISR testing currently relies on one-off fake images that do not 234 accurately mimic the full set of detector effects. This class 235 uses the test camera/detector/amplifier structure defined in 236 `lsst.afw.cameraGeom.testUtils` to avoid making the test data 237 dependent on any of the actual obs package formats. 239 ConfigClass = IsrMockConfig
240 _DefaultName =
"isrMock" 244 self.
rng = np.random.RandomState(self.config.rngSeed)
246 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
247 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
248 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
249 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
250 [1e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
251 [1e-2, 0.0, 0.0, 2.2e-2, 0.0, 0.0, 0.0, 0.0],
252 [1e-2, 5e-3, 5e-4, 3e-3, 4e-2, 5e-3, 5e-3, 0.0]])
255 [4., 16., 26., 16., 4.],
256 [7., 26., 41., 26., 7.],
257 [4., 16., 26., 16., 4.],
258 [1., 4., 7., 4., 1.]]) / 273.0
261 r"""Generate a mock ISR product, and return it. 265 image : `lsst.afw.image.Exposure` 266 Simulated ISR image with signals added. 268 Simulated ISR data products. 270 Returned if no valid configuration was found. 275 Raised if both doGenerateImage and doGenerateData are specified. 277 if self.config.doGenerateImage
and self.config.doGenerateData:
278 raise RuntimeError(
"Only one of doGenerateImage and doGenerateData may be specified.")
279 elif self.config.doGenerateImage:
281 elif self.config.doGenerateData:
287 r"""Generate simulated ISR data. 289 Currently, only the class defined crosstalk coefficient 290 matrix, brighter-fatter kernel, a constant unity transmission 291 curve, or a simple single-entry defect list can be generated. 296 Simulated ISR data product. 298 if sum(map(bool, [self.config.doBrighterFatter,
299 self.config.doDefects,
300 self.config.doTransmissionCurve,
301 self.config.doCrosstalkCoeffs])) != 1:
302 raise RuntimeError(
"Only one data product can be generated at a time.")
303 elif self.config.doBrighterFatter
is True:
305 elif self.config.doDefects
is True:
307 elif self.config.doTransmissionCurve
is True:
309 elif self.config.doCrosstalkCoeffs
is True:
315 r"""Generate a simple Gaussian brighter-fatter kernel. 319 kernel : `numpy.ndarray` 320 Simulated brighter-fatter kernel. 325 r"""Generate a simple single-entry defect list. 329 defectList : `list` of `Defects` 330 Simulated defect list 333 bbox = afwGeom.BoxI(afwGeom.PointI(0, 0),
334 afwGeom.ExtentI(40, 50))
335 defectList.append(
Defect(bbox))
339 r"""Generate the simulated crosstalk coefficients. 343 coeffs : `numpy.ndarray` 344 Simulated crosstalk coefficients. 350 r"""Generate a simulated flat transmission curve. 354 transmission : `lsst.afw.image.TransmissionCurve` 355 Simulated transmission curve. 358 return afwImage.TransmissionCurve.makeIdentity()
361 r"""Generate a simulated ISR image. 365 exposure : `lsst.afw.image.Exposure` or `dict` 366 Simulated ISR image data. 370 This method currently constructs a "raw" data image by: 371 * Generating a simulated sky with noise 372 * Adding a single Gaussian "star" 373 * Adding the fringe signal 374 * Multiplying the frame by the simulated flat 375 * Adding dark current (and noise) 376 * Adding a bias offset (and noise) 377 * Adding an overscan gradient parallel to the pixel y-axis 378 * Simulating crosstalk by adding a scaled version of each 379 amplifier to each other amplifier. 381 The exposure with image data constructed this way is in one of 383 * A single image, with overscan and prescan regions retained 384 * A single image, with overscan and prescan regions trimmed 385 * A `dict`, containing the amplifer data indexed by the 388 The nonlinearity, CTE, and brighter fatter are currently not 391 Note that this method generates an image in the reverse 392 direction as the ISR processing, as the output image here has 393 had a series of instrument effects added to an idealized 398 for idx, amp
in enumerate(exposure.getDetector()):
400 if self.config.isTrimmed
is True:
403 bbox = amp.getRawDataBBox()
405 ampData = exposure.image[bbox]
407 if self.config.doAddSky
is True:
408 self.
amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
410 if self.config.doAddSource
is True:
411 for sourceAmp, sourceFlux, sourceX, sourceY
in zip(self.config.sourceAmp,
412 self.config.sourceFlux,
414 self.config.sourceY):
418 if self.config.doAddFringe
is True:
421 if self.config.doAddFlat
is True:
422 if ampData.getArray().sum() == 0.0:
424 u0 = exposure.getDimensions().getX()
425 v0 = exposure.getDimensions().getY()
428 if self.config.doAddDark
is True:
430 self.config.darkRate * self.config.darkTime / self.config.gain,
431 np.sqrt(self.config.darkRate *
432 self.config.darkTime / self.config.gain))
434 if self.config.doAddCrosstalk
is True:
435 for idxS, ampS
in enumerate(exposure.getDetector()):
436 for idxT, ampT
in enumerate(exposure.getDetector()):
437 if self.config.isTrimmed
is True:
438 ampDataS = exposure.image[ampS.getBBox()]
439 ampDataT = exposure.image[ampT.getBBox()]
441 ampDataS = exposure.image[ampS.getRawDataBBox()]
442 ampDataT = exposure.image[ampT.getRawDataBBox()]
445 for amp
in exposure.getDetector():
447 if self.config.isTrimmed
is True:
450 bbox = amp.getRawDataBBox()
452 ampData = exposure.image[bbox]
454 if self.config.doAddBias
is True:
456 self.config.readNoise / self.config.gain)
458 if self.config.doAddOverscan
is True:
459 oscanBBox = amp.getRawHorizontalOverscanBBox()
460 oscanData = exposure.image[oscanBBox]
462 self.config.readNoise / self.config.gain)
465 1.0 * self.config.overscanScale)
467 1.0 * self.config.overscanScale)
469 if self.config.doGenerateAmpDict
is True:
471 for amp
in exposure.getDetector():
472 expDict[amp.getName()] = exposure
479 r"""Construct a test camera object. 483 camera : `lsst.afw.cameraGeom.camera` 486 cameraWrapper = afwTestUtils.CameraWrapper(self.config.isLsstLike)
487 camera = cameraWrapper.camera
491 r"""Construct a test exposure. 493 The test exposure has a simple WCS set, as well as a list of 494 unlikely header keywords that can be removed during ISR 495 processing to exercise that code. 499 exposure : `lsst.afw.exposure.Exposure` 500 Construct exposure containing masked image of the 504 detector = camera[self.config.detectorIndex]
505 image = afwUtils.makeImageFromCcd(detector,
506 isTrimmed=self.config.isTrimmed,
510 imageFactory=afwImage.ImageF)
512 var = afwImage.ImageF(image.getDimensions())
513 mask = afwImage.Mask(image.getDimensions())
516 maskedImage = afwImage.makeMaskedImage(image, mask, var)
517 exposure = afwImage.makeExposure(maskedImage)
518 exposure.setDetector(detector)
519 exposure.setWcs(self.
getWcs())
521 visitInfo = afwImage.VisitInfo(exposureTime=self.config.expTime, darkTime=self.config.darkTime)
522 exposure.getInfo().setVisitInfo(visitInfo)
524 metadata = exposure.getMetadata()
525 metadata.add(
"SHEEP", 7.3,
"number of sheep on farm")
526 metadata.add(
"MONKEYS", 155,
"monkeys per tree")
527 metadata.add(
"VAMPIRES", 4,
"How scary are vampires.")
529 for amp
in exposure.getDetector():
530 amp.setLinearityCoeffs((0., 1., 0., 0.))
531 amp.setLinearityType(
"Polynomial")
532 amp.setGain(self.config.gain)
534 exposure.image.array[:] = np.zeros(exposure.getImage().getDimensions()).transpose()
535 exposure.mask.array[:] = np.zeros(exposure.getMask().getDimensions()).transpose()
536 exposure.variance.array[:] = np.zeros(exposure.getVariance().getDimensions()).transpose()
541 r"""Construct a dummy WCS object. 543 Taken from the deprecated ip_isr/examples/exampleUtils.py. 545 This is not guaranteed, given the distortion and pixel scale 546 listed in the afwTestUtils camera definition. 550 wcs : `lsst.afw.geom.SkyWcs` 553 return afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(0.0, 100.0),
554 crval=afwGeom.SpherePoint(45.0, 25.0, afwGeom.degrees),
555 cdMatrix=afwGeom.makeCdMatrix(scale=1.0*afwGeom.degrees))
558 r"""Convert between a local amplifier coordinate and the full 563 ampData : `lsst.afw.image.ImageF` 564 Amplifier image to use for conversions. 566 X-coordinate of the point to transform. 568 Y-coordinate of the point to transform. 573 Transformed x-coordinate. 575 Transformed y-coordinate. 579 The output is transposed intentionally here, to match the 580 internal transpose between numpy and afw.image coordinates. 582 u = x + ampData.getBBox().getBeginX()
583 v = y + ampData.getBBox().getBeginY()
589 r"""Add Gaussian noise to an amplifier's image data. 591 This method operates in the amplifier coordinate frame. 595 ampData : `lsst.afw.image.ImageF` 596 Amplifier image to operate on. 598 Mean value of the Gaussian noise. 600 Sigma of the Gaussian noise. 602 ampArr = ampData.array
603 ampArr[:] = ampArr[:] + self.
rng.normal(mean, sigma,
604 size=ampData.getDimensions()).transpose()
607 r"""Add a y-axis linear gradient to an amplifier's image data. 609 This method operates in the amplifier coordinate frame. 613 ampData : `lsst.afw.image.ImageF` 614 Amplifier image to operate on. 616 Start value of the gradient (at y=0). 618 End value of the gradient (at y=ymax). 620 nPixY = ampData.getDimensions().getY()
621 ampArr = ampData.array
622 ampArr[:] = ampArr[:] + (np.interp(range(nPixY), (0, nPixY - 1), (start, end)).reshape(nPixY, 1) +
623 np.zeros(ampData.getDimensions()).transpose())
626 r"""Add a single Gaussian source to an amplifier. 628 This method operates in the amplifier coordinate frame. 632 ampData : `lsst.afw.image.ImageF` 633 Amplifier image to operate on. 635 Peak flux of the source to add. 637 X-coordinate of the source peak. 639 Y-coordinate of the source peak. 641 for x
in range(0, ampData.getDimensions().getX()):
642 for y
in range(0, ampData.getDimensions().getY()):
643 ampData.array[y][x] = (ampData.array[y][x] +
644 scale * np.exp(-0.5 * ((x - x0)**2 + (y - y0)**2) / 3.0**2))
647 r"""Add a scaled copy of an amplifier to another, simulating crosstalk. 649 This method operates in the amplifier coordinate frame. 653 ampDataSource : `lsst.afw.image.ImageF` 654 Amplifier image to add scaled copy from. 655 ampDataTarget : `lsst.afw.image.ImageF` 656 Amplifier image to add scaled copy to. 658 Flux scale of the copy to add to the target. 662 This simulates simple crosstalk between amplifiers. 664 ampDataTarget.array[:] = (ampDataTarget.array[:] +
665 scale * ampDataSource.array[:])
669 r"""Add a fringe-like ripple pattern to an amplifier's image data. 673 amp : `lsst.afw.ampInfo.AmpInfoRecord` 674 Amplifier to operate on. Needed for amp<->exp coordinate transforms. 675 ampData : `lsst.afw.image.ImageF` 676 Amplifier image to operate on. 678 Peak intensity scaling for the ripple. 682 This uses an offset sinc function to generate a ripple 683 pattern. True fringes have much finer structure, but this 684 pattern should be visually identifiable. The (x, y) 685 coordinates are in the frame of the amplifier, and (u, v) in 686 the frame of the full trimmed image. 688 for x
in range(0, ampData.getDimensions().getX()):
689 for y
in range(0, ampData.getDimensions().getY()):
691 ampData.array[y][x] = (ampData.array[y][x] +
692 scale * np.sinc(((u - 100)/150)**2 + (v / 150)**2))
695 r"""Multiply an amplifier's image data by a flat-like pattern. 699 amp : `lsst.afw.ampInfo.AmpInfoRecord` 700 Amplifier to operate on. Needed for amp<->exp coordinate transforms. 701 ampData : `lsst.afw.image.ImageF` 702 Amplifier image to operate on. 704 Fractional drop from center to edge of detector along x-axis. 706 Peak location in detector coordinates. 708 Peak location in detector coordinates. 712 This uses a 2-d Gaussian to simulate an illumination pattern 713 that falls off towards the edge of the detector. The (x, y) 714 coordinates are in the frame of the amplifier, and (u, v) in 715 the frame of the full trimmed image. 718 raise RuntimeError(
"Flat fractional drop cannot be greater than 1.0")
720 sigma = u0 / np.sqrt(-2.0 * np.log(fracDrop))
722 for x
in range(0, ampData.getDimensions().getX()):
723 for y
in range(0, ampData.getDimensions().getY()):
725 f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
726 ampData.array[y][x] = (ampData.array[y][x] * f)
730 r"""Generate a raw exposure suitable for ISR. 734 self.config.isTrimmed =
False 735 self.config.doGenerateImage =
True 736 self.config.doGenerateAmpDict =
False 737 self.config.doAddOverscan =
True 738 self.config.doAddSky =
True 739 self.config.doAddSource =
True 740 self.config.doAddCrosstalk =
False 741 self.config.doAddBias =
True 742 self.config.doAddDark =
True 746 r"""Generate a trimmed raw exposure. 750 self.config.isTrimmed =
True 751 self.config.doAddOverscan =
False 755 r"""Generate a trimmed raw exposure. 759 self.config.isTrimmed =
True 760 self.config.doAddOverscan =
False 761 self.config.doAddBias =
True 762 self.config.doAddDark =
False 763 self.config.doAddFlat =
False 764 self.config.doAddFringe =
False 765 self.config.doAddCrosstalk =
True 766 self.config.biasLevel = 0.0
767 self.config.readNoise = 10.0
771 r"""Generate a raw exposure dict suitable for ISR. 775 self.config.doGenerateAmpDict =
True 779 r"""Parent class for those that make master calibrations. 783 self.config.isTrimmed =
True 784 self.config.doGenerateImage =
True 785 self.config.doAddOverscan =
False 786 self.config.doAddSky =
False 787 self.config.doAddSource =
False 788 self.config.doAddCrosstalk =
False 790 self.config.doAddBias =
False 791 self.config.doAddDark =
False 792 self.config.doAddFlat =
False 793 self.config.doAddFringe =
False 797 r"""Simulated master bias calibration. 801 self.config.doAddBias =
True 802 self.config.readNoise = 10.0
806 r"""Simulated master dark calibration. 810 self.config.doAddDark =
True 811 self.config.darkTime = 1.0
815 r"""Simulated master flat calibration. 819 self.config.doAddFlat =
True 823 r"""Simulated master fringe calibration. 827 self.config.doAddFringe =
True 831 r"""Simulated untrimmed master fringe calibration. 835 self.config.isTrimmed =
False 839 r"""Simulated brighter-fatter kernel. 843 self.config.doGenerateImage =
False 844 self.config.doGenerateData =
True 845 self.config.doBrighterFatter =
True 846 self.config.doDefects =
False 847 self.config.doCrosstalkCoeffs =
False 848 self.config.doTransmissionCurve =
False 852 r"""Simulated defect list. 856 self.config.doGenerateImage =
False 857 self.config.doGenerateData =
True 858 self.config.doBrighterFatter =
False 859 self.config.doDefects =
True 860 self.config.doCrosstalkCoeffs =
False 861 self.config.doTransmissionCurve =
False 865 r"""Simulated crosstalk coefficient matrix. 869 self.config.doGenerateImage =
False 870 self.config.doGenerateData =
True 871 self.config.doBrighterFatter =
False 872 self.config.doDefects =
False 873 self.config.doCrosstalkCoeffs =
True 874 self.config.doTransmissionCurve =
False 878 r"""Simulated transmission curve. 882 self.config.doGenerateImage =
False 883 self.config.doGenerateData =
True 884 self.config.doBrighterFatter =
False 885 self.config.doDefects =
False 886 self.config.doCrosstalkCoeffs =
False 887 self.config.doTransmissionCurve =
True 891 r"""Simulated gen2 butler data ref. 893 Currently only supports get and put operations, which are most 894 likely to be called for data in ISR processing. 897 dataId =
"isrMock Fake Data" 905 if 'config' in kwargs.keys():
913 self.
config.doGenerateImage =
True 914 self.
config.doGenerateData =
False 919 self.
config.doGenerateImage =
False 920 self.
config.doGenerateData =
True 922 def get(self, dataType, **kwargs):
923 r"""Return an appropriate data product. 928 Type of data product to return. 932 mock : IsrMock.run() result 935 if "_filename" in dataType:
937 return tempfile.mktemp(),
"mock" 938 elif 'transmission_' in dataType:
941 elif dataType ==
'ccdExposureId':
944 elif dataType ==
'camera':
947 elif dataType ==
'raw':
950 elif dataType ==
'bias':
953 elif dataType ==
'dark':
956 elif dataType ==
'flat':
959 elif dataType ==
'fringe':
962 elif dataType ==
'defects':
965 elif dataType ==
'bfKernel':
968 elif dataType ==
'linearizer':
970 elif dataType ==
'crosstalkSources':
973 raise RuntimeError(
"ISR DataRefMock cannot return %s.", dataType)
975 def put(self, exposure, filename):
976 r"""Write an exposure to a FITS file. 980 exposure : `lsst.afw.image.Exposure` 981 Image data to write out. 983 Base name of the output file. 985 exposure.writeFits(filename+
".fits")
def amplifierAddNoise(self, ampData, mean, sigma)
def __init__(self, kwargs)
def amplifierAddFringe(self, amp, ampData, scale)
def __init__(self, kwargs)
def __init__(self, kwargs)
def amplifierAddSource(self, ampData, scale, x0, y0)
def __init__(self, kwargs)
def __init__(self, kwargs)
def amplifierAddYGradient(self, ampData, start, end)
def put(self, exposure, filename)
def __init__(self, kwargs)
def __init__(self, kwargs)
def __init__(self, kwargs)
def __init__(self, kwargs)
def __init__(self, kwargs)
def localCoordToExpCoord(self, ampData, x, y)
def __init__(self, kwargs)
def amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0)
def __init__(self, kwargs)
def get(self, dataType, kwargs)
def __init__(self, kwargs)
def makeTransmissionCurve(self)
def __init__(self, kwargs)
def __init__(self, kwargs)
def amplifierAddCT(self, ampDataSource, ampDataTarget, scale)
def makeCrosstalkCoeff(self)
def __init__(self, kwargs)