35 __all__ = [
"IsrMockConfig",
"IsrMock",
"RawMock",
"TrimmedRawMock",
"RawDictMock",
36 "CalibratedRawMock",
"MasterMock",
37 "BiasMock",
"DarkMock",
"FlatMock",
"FringeMock",
"UntrimmedFringeMock",
38 "BfKernelMock",
"DefectMock",
"CrosstalkCoeffMock",
"TransmissionMock",
43 r"""Configuration parameters for isrMock. 45 These parameters produce generic fixed position signals from 46 various sources, and combine them in a way that matches how those 47 signals are combined to create real data. The camera used is the 48 test camera defined by the afwUtils code. 51 isLsstLike = pexConfig.Field(
54 doc=
"If True, products have one raw image per amplifier, otherwise, one raw image per detector.",
56 isTrimmed = pexConfig.Field(
59 doc=
"If True, amplifiers have been trimmed and mosaicked to remove regions outside the data BBox.",
61 detectorIndex = pexConfig.Field(
64 doc=
"Index for the detector to use. The default value uses a standard 2x4 array of amps.",
66 rngSeed = pexConfig.Field(
69 doc=
"Seed for random number generator used to add noise.",
72 gain = pexConfig.Field(
75 doc=
"Gain for simulated data in e^-/DN.",
77 readNoise = pexConfig.Field(
80 doc=
"Read noise of the detector in e-.",
82 expTime = pexConfig.Field(
85 doc=
"Exposure time for simulated data.",
89 skyLevel = pexConfig.Field(
92 doc=
"Background contribution to be generated from 'the sky' in DN.",
94 sourceFlux = pexConfig.ListField(
97 doc=
"Peak flux level (in DN) of simulated 'astronomical sources'.",
99 sourceAmp = pexConfig.ListField(
102 doc=
"Amplifier to place simulated 'astronomical sources'.",
104 sourceX = pexConfig.ListField(
107 doc=
"Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
109 sourceY = pexConfig.ListField(
112 doc=
"Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
114 overscanScale = pexConfig.Field(
117 doc=
"Amplitude (in DN) of the ramp function to add to overscan data.",
119 biasLevel = pexConfig.Field(
122 doc=
"Background contribution to be generated from the bias offset in DN.",
124 darkRate = pexConfig.Field(
127 doc=
"Background level contribution (in e-/s) to be generated from dark current.",
129 darkTime = pexConfig.Field(
132 doc=
"Exposure time for the dark current contribution.",
134 flatDrop = pexConfig.Field(
137 doc=
"Fractional flux drop due to flat from center to edge of detector along x-axis.",
139 fringeScale = pexConfig.Field(
142 doc=
"Peak flux for the fringe ripple in DN.",
146 doAddSky = pexConfig.Field(
149 doc=
"Apply 'sky' signal to output image.",
151 doAddSource = pexConfig.Field(
154 doc=
"Add simulated source to output image.",
156 doAddCrosstalk = pexConfig.Field(
159 doc=
"Apply simulated crosstalk to output image. This cannot be corrected by ISR, " 160 "as detector.hasCrosstalk()==False.",
162 doAddOverscan = pexConfig.Field(
165 doc=
"If untrimmed, add overscan ramp to overscan and data regions.",
167 doAddBias = pexConfig.Field(
170 doc=
"Add bias signal to data.",
172 doAddDark = pexConfig.Field(
175 doc=
"Add dark signal to data.",
177 doAddFlat = pexConfig.Field(
180 doc=
"Add flat signal to data.",
182 doAddFringe = pexConfig.Field(
185 doc=
"Add fringe signal to data.",
189 doTransmissionCurve = pexConfig.Field(
192 doc=
"Return a simulated transmission curve.",
194 doDefects = pexConfig.Field(
197 doc=
"Return a simulated defect list.",
199 doBrighterFatter = pexConfig.Field(
202 doc=
"Return a simulated brighter-fatter kernel.",
204 doCrosstalkCoeffs = pexConfig.Field(
207 doc=
"Return the matrix of crosstalk coefficients.",
209 doDataRef = pexConfig.Field(
212 doc=
"Return a simulated gen2 butler dataRef.",
214 doGenerateImage = pexConfig.Field(
217 doc=
"Return the generated output image if True.",
219 doGenerateData = pexConfig.Field(
222 doc=
"Return a non-image data structure if True.",
224 doGenerateAmpDict = pexConfig.Field(
227 doc=
"Return a dict of exposure amplifiers instead of an afwImage.Exposure.",
232 r"""Class to generate consistent mock images for ISR testing. 234 ISR testing currently relies on one-off fake images that do not 235 accurately mimic the full set of detector effects. This class 236 uses the test camera/detector/amplifier structure defined in 237 `lsst.afw.cameraGeom.testUtils` to avoid making the test data 238 dependent on any of the actual obs package formats. 240 ConfigClass = IsrMockConfig
241 _DefaultName =
"isrMock" 245 self.
rng = np.random.RandomState(self.config.rngSeed)
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, 0.0, 0.0, 0.0, 0.0, 0.0],
252 [1e-2, 0.0, 0.0, 2.2e-2, 0.0, 0.0, 0.0, 0.0],
253 [1e-2, 5e-3, 5e-4, 3e-3, 4e-2, 5e-3, 5e-3, 0.0]])
256 [4., 16., 26., 16., 4.],
257 [7., 26., 41., 26., 7.],
258 [4., 16., 26., 16., 4.],
259 [1., 4., 7., 4., 1.]]) / 273.0
262 r"""Generate a mock ISR product, and return it. 266 image : `lsst.afw.image.Exposure` 267 Simulated ISR image with signals added. 269 Simulated ISR data products. 271 Returned if no valid configuration was found. 276 Raised if both doGenerateImage and doGenerateData are specified. 278 if self.config.doGenerateImage
and self.config.doGenerateData:
279 raise RuntimeError(
"Only one of doGenerateImage and doGenerateData may be specified.")
280 elif self.config.doGenerateImage:
282 elif self.config.doGenerateData:
288 r"""Generate simulated ISR data. 290 Currently, only the class defined crosstalk coefficient 291 matrix, brighter-fatter kernel, a constant unity transmission 292 curve, or a simple single-entry defect list can be generated. 297 Simulated ISR data product. 299 if sum(map(bool, [self.config.doBrighterFatter,
300 self.config.doDefects,
301 self.config.doTransmissionCurve,
302 self.config.doCrosstalkCoeffs])) != 1:
303 raise RuntimeError(
"Only one data product can be generated at a time.")
304 elif self.config.doBrighterFatter
is True:
306 elif self.config.doDefects
is True:
308 elif self.config.doTransmissionCurve
is True:
310 elif self.config.doCrosstalkCoeffs
is True:
316 r"""Generate a simple Gaussian brighter-fatter kernel. 320 kernel : `numpy.ndarray` 321 Simulated brighter-fatter kernel. 326 r"""Generate a simple single-entry defect list. 330 defectList : `lsst.meas.algorithms.Defects` 331 Simulated defect list 337 r"""Generate the simulated crosstalk coefficients. 341 coeffs : `numpy.ndarray` 342 Simulated crosstalk coefficients. 348 r"""Generate a simulated flat transmission curve. 352 transmission : `lsst.afw.image.TransmissionCurve` 353 Simulated transmission curve. 356 return afwImage.TransmissionCurve.makeIdentity()
359 r"""Generate a simulated ISR image. 363 exposure : `lsst.afw.image.Exposure` or `dict` 364 Simulated ISR image data. 368 This method currently constructs a "raw" data image by: 369 * Generating a simulated sky with noise 370 * Adding a single Gaussian "star" 371 * Adding the fringe signal 372 * Multiplying the frame by the simulated flat 373 * Adding dark current (and noise) 374 * Adding a bias offset (and noise) 375 * Adding an overscan gradient parallel to the pixel y-axis 376 * Simulating crosstalk by adding a scaled version of each 377 amplifier to each other amplifier. 379 The exposure with image data constructed this way is in one of 381 * A single image, with overscan and prescan regions retained 382 * A single image, with overscan and prescan regions trimmed 383 * A `dict`, containing the amplifer data indexed by the 386 The nonlinearity, CTE, and brighter fatter are currently not 389 Note that this method generates an image in the reverse 390 direction as the ISR processing, as the output image here has 391 had a series of instrument effects added to an idealized 396 for idx, amp
in enumerate(exposure.getDetector()):
398 if self.config.isTrimmed
is True:
401 bbox = amp.getRawDataBBox()
403 ampData = exposure.image[bbox]
405 if self.config.doAddSky
is True:
406 self.
amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
408 if self.config.doAddSource
is True:
409 for sourceAmp, sourceFlux, sourceX, sourceY
in zip(self.config.sourceAmp,
410 self.config.sourceFlux,
412 self.config.sourceY):
416 if self.config.doAddFringe
is True:
419 if self.config.doAddFlat
is True:
420 if ampData.getArray().sum() == 0.0:
422 u0 = exposure.getDimensions().getX()
423 v0 = exposure.getDimensions().getY()
426 if self.config.doAddDark
is True:
428 self.config.darkRate * self.config.darkTime / self.config.gain,
429 np.sqrt(self.config.darkRate *
430 self.config.darkTime / self.config.gain))
432 if self.config.doAddCrosstalk
is True:
433 for idxS, ampS
in enumerate(exposure.getDetector()):
434 for idxT, ampT
in enumerate(exposure.getDetector()):
435 if self.config.isTrimmed
is True:
436 ampDataS = exposure.image[ampS.getBBox()]
437 ampDataT = exposure.image[ampT.getBBox()]
439 ampDataS = exposure.image[ampS.getRawDataBBox()]
440 ampDataT = exposure.image[ampT.getRawDataBBox()]
443 for amp
in exposure.getDetector():
445 if self.config.isTrimmed
is True:
448 bbox = amp.getRawDataBBox()
450 ampData = exposure.image[bbox]
452 if self.config.doAddBias
is True:
454 self.config.readNoise / self.config.gain)
456 if self.config.doAddOverscan
is True:
457 oscanBBox = amp.getRawHorizontalOverscanBBox()
458 oscanData = exposure.image[oscanBBox]
460 self.config.readNoise / self.config.gain)
463 1.0 * self.config.overscanScale)
465 1.0 * self.config.overscanScale)
467 if self.config.doGenerateAmpDict
is True:
469 for amp
in exposure.getDetector():
470 expDict[amp.getName()] = exposure
477 r"""Construct a test camera object. 481 camera : `lsst.afw.cameraGeom.camera` 484 cameraWrapper = afwTestUtils.CameraWrapper(self.config.isLsstLike)
485 camera = cameraWrapper.camera
489 r"""Construct a test exposure. 491 The test exposure has a simple WCS set, as well as a list of 492 unlikely header keywords that can be removed during ISR 493 processing to exercise that code. 497 exposure : `lsst.afw.exposure.Exposure` 498 Construct exposure containing masked image of the 502 detector = camera[self.config.detectorIndex]
503 image = afwUtils.makeImageFromCcd(detector,
504 isTrimmed=self.config.isTrimmed,
508 imageFactory=afwImage.ImageF)
510 var = afwImage.ImageF(image.getDimensions())
511 mask = afwImage.Mask(image.getDimensions())
514 maskedImage = afwImage.makeMaskedImage(image, mask, var)
515 exposure = afwImage.makeExposure(maskedImage)
516 exposure.setDetector(detector)
517 exposure.setWcs(self.
getWcs())
519 visitInfo = afwImage.VisitInfo(exposureTime=self.config.expTime, darkTime=self.config.darkTime)
520 exposure.getInfo().setVisitInfo(visitInfo)
522 metadata = exposure.getMetadata()
523 metadata.add(
"SHEEP", 7.3,
"number of sheep on farm")
524 metadata.add(
"MONKEYS", 155,
"monkeys per tree")
525 metadata.add(
"VAMPIRES", 4,
"How scary are vampires.")
527 for amp
in exposure.getDetector():
528 amp.setLinearityCoeffs((0., 1., 0., 0.))
529 amp.setLinearityType(
"Polynomial")
530 amp.setGain(self.config.gain)
532 exposure.image.array[:] = np.zeros(exposure.getImage().getDimensions()).transpose()
533 exposure.mask.array[:] = np.zeros(exposure.getMask().getDimensions()).transpose()
534 exposure.variance.array[:] = np.zeros(exposure.getVariance().getDimensions()).transpose()
539 r"""Construct a dummy WCS object. 541 Taken from the deprecated ip_isr/examples/exampleUtils.py. 543 This is not guaranteed, given the distortion and pixel scale 544 listed in the afwTestUtils camera definition. 548 wcs : `lsst.afw.geom.SkyWcs` 553 cdMatrix=afwGeom.makeCdMatrix(scale=1.0*lsst.geom.degrees))
556 r"""Convert between a local amplifier coordinate and the full 561 ampData : `lsst.afw.image.ImageF` 562 Amplifier image to use for conversions. 564 X-coordinate of the point to transform. 566 Y-coordinate of the point to transform. 571 Transformed x-coordinate. 573 Transformed y-coordinate. 577 The output is transposed intentionally here, to match the 578 internal transpose between numpy and afw.image coordinates. 580 u = x + ampData.getBBox().getBeginX()
581 v = y + ampData.getBBox().getBeginY()
587 r"""Add Gaussian noise to an amplifier's image data. 589 This method operates in the amplifier coordinate frame. 593 ampData : `lsst.afw.image.ImageF` 594 Amplifier image to operate on. 596 Mean value of the Gaussian noise. 598 Sigma of the Gaussian noise. 600 ampArr = ampData.array
601 ampArr[:] = ampArr[:] + self.
rng.normal(mean, sigma,
602 size=ampData.getDimensions()).transpose()
605 r"""Add a y-axis linear gradient to an amplifier's image data. 607 This method operates in the amplifier coordinate frame. 611 ampData : `lsst.afw.image.ImageF` 612 Amplifier image to operate on. 614 Start value of the gradient (at y=0). 616 End value of the gradient (at y=ymax). 618 nPixY = ampData.getDimensions().getY()
619 ampArr = ampData.array
620 ampArr[:] = ampArr[:] + (np.interp(range(nPixY), (0, nPixY - 1), (start, end)).reshape(nPixY, 1) +
621 np.zeros(ampData.getDimensions()).transpose())
624 r"""Add a single Gaussian source to an amplifier. 626 This method operates in the amplifier coordinate frame. 630 ampData : `lsst.afw.image.ImageF` 631 Amplifier image to operate on. 633 Peak flux of the source to add. 635 X-coordinate of the source peak. 637 Y-coordinate of the source peak. 639 for x
in range(0, ampData.getDimensions().getX()):
640 for y
in range(0, ampData.getDimensions().getY()):
641 ampData.array[y][x] = (ampData.array[y][x] +
642 scale * np.exp(-0.5 * ((x - x0)**2 + (y - y0)**2) / 3.0**2))
645 r"""Add a scaled copy of an amplifier to another, simulating crosstalk. 647 This method operates in the amplifier coordinate frame. 651 ampDataSource : `lsst.afw.image.ImageF` 652 Amplifier image to add scaled copy from. 653 ampDataTarget : `lsst.afw.image.ImageF` 654 Amplifier image to add scaled copy to. 656 Flux scale of the copy to add to the target. 660 This simulates simple crosstalk between amplifiers. 662 ampDataTarget.array[:] = (ampDataTarget.array[:] +
663 scale * ampDataSource.array[:])
667 r"""Add a fringe-like ripple pattern to an amplifier's image data. 671 amp : `lsst.afw.ampInfo.AmpInfoRecord` 672 Amplifier to operate on. Needed for amp<->exp coordinate transforms. 673 ampData : `lsst.afw.image.ImageF` 674 Amplifier image to operate on. 676 Peak intensity scaling for the ripple. 680 This uses an offset sinc function to generate a ripple 681 pattern. True fringes have much finer structure, but this 682 pattern should be visually identifiable. The (x, y) 683 coordinates are in the frame of the amplifier, and (u, v) in 684 the frame of the full trimmed image. 686 for x
in range(0, ampData.getDimensions().getX()):
687 for y
in range(0, ampData.getDimensions().getY()):
689 ampData.array[y][x] = (ampData.array[y][x] +
690 scale * np.sinc(((u - 100)/150)**2 + (v / 150)**2))
693 r"""Multiply an amplifier's image data by a flat-like pattern. 697 amp : `lsst.afw.ampInfo.AmpInfoRecord` 698 Amplifier to operate on. Needed for amp<->exp coordinate transforms. 699 ampData : `lsst.afw.image.ImageF` 700 Amplifier image to operate on. 702 Fractional drop from center to edge of detector along x-axis. 704 Peak location in detector coordinates. 706 Peak location in detector coordinates. 710 This uses a 2-d Gaussian to simulate an illumination pattern 711 that falls off towards the edge of the detector. The (x, y) 712 coordinates are in the frame of the amplifier, and (u, v) in 713 the frame of the full trimmed image. 716 raise RuntimeError(
"Flat fractional drop cannot be greater than 1.0")
718 sigma = u0 / np.sqrt(-2.0 * np.log(fracDrop))
720 for x
in range(0, ampData.getDimensions().getX()):
721 for y
in range(0, ampData.getDimensions().getY()):
723 f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
724 ampData.array[y][x] = (ampData.array[y][x] * f)
728 r"""Generate a raw exposure suitable for ISR. 732 self.config.isTrimmed =
False 733 self.config.doGenerateImage =
True 734 self.config.doGenerateAmpDict =
False 735 self.config.doAddOverscan =
True 736 self.config.doAddSky =
True 737 self.config.doAddSource =
True 738 self.config.doAddCrosstalk =
False 739 self.config.doAddBias =
True 740 self.config.doAddDark =
True 744 r"""Generate a trimmed raw exposure. 748 self.config.isTrimmed =
True 749 self.config.doAddOverscan =
False 753 r"""Generate a trimmed raw exposure. 757 self.config.isTrimmed =
True 758 self.config.doAddOverscan =
False 759 self.config.doAddBias =
True 760 self.config.doAddDark =
False 761 self.config.doAddFlat =
False 762 self.config.doAddFringe =
False 763 self.config.doAddCrosstalk =
True 764 self.config.biasLevel = 0.0
765 self.config.readNoise = 10.0
769 r"""Generate a raw exposure dict suitable for ISR. 773 self.config.doGenerateAmpDict =
True 777 r"""Parent class for those that make master calibrations. 781 self.config.isTrimmed =
True 782 self.config.doGenerateImage =
True 783 self.config.doAddOverscan =
False 784 self.config.doAddSky =
False 785 self.config.doAddSource =
False 786 self.config.doAddCrosstalk =
False 788 self.config.doAddBias =
False 789 self.config.doAddDark =
False 790 self.config.doAddFlat =
False 791 self.config.doAddFringe =
False 795 r"""Simulated master bias calibration. 799 self.config.doAddBias =
True 800 self.config.readNoise = 10.0
804 r"""Simulated master dark calibration. 808 self.config.doAddDark =
True 809 self.config.darkTime = 1.0
813 r"""Simulated master flat calibration. 817 self.config.doAddFlat =
True 821 r"""Simulated master fringe calibration. 825 self.config.doAddFringe =
True 829 r"""Simulated untrimmed master fringe calibration. 833 self.config.isTrimmed =
False 837 r"""Simulated brighter-fatter kernel. 841 self.config.doGenerateImage =
False 842 self.config.doGenerateData =
True 843 self.config.doBrighterFatter =
True 844 self.config.doDefects =
False 845 self.config.doCrosstalkCoeffs =
False 846 self.config.doTransmissionCurve =
False 850 r"""Simulated defect list. 854 self.config.doGenerateImage =
False 855 self.config.doGenerateData =
True 856 self.config.doBrighterFatter =
False 857 self.config.doDefects =
True 858 self.config.doCrosstalkCoeffs =
False 859 self.config.doTransmissionCurve =
False 863 r"""Simulated crosstalk coefficient matrix. 867 self.config.doGenerateImage =
False 868 self.config.doGenerateData =
True 869 self.config.doBrighterFatter =
False 870 self.config.doDefects =
False 871 self.config.doCrosstalkCoeffs =
True 872 self.config.doTransmissionCurve =
False 876 r"""Simulated transmission curve. 880 self.config.doGenerateImage =
False 881 self.config.doGenerateData =
True 882 self.config.doBrighterFatter =
False 883 self.config.doDefects =
False 884 self.config.doCrosstalkCoeffs =
False 885 self.config.doTransmissionCurve =
True 889 r"""Simulated gen2 butler data ref. 891 Currently only supports get and put operations, which are most 892 likely to be called for data in ISR processing. 895 dataId =
"isrMock Fake Data" 903 if 'config' in kwargs.keys():
911 self.
config.doGenerateImage =
True 912 self.
config.doGenerateData =
False 917 self.
config.doGenerateImage =
False 918 self.
config.doGenerateData =
True 920 def get(self, dataType, **kwargs):
921 r"""Return an appropriate data product. 926 Type of data product to return. 930 mock : IsrMock.run() result 933 if "_filename" in dataType:
935 return tempfile.mktemp(),
"mock" 936 elif 'transmission_' in dataType:
939 elif dataType ==
'ccdExposureId':
942 elif dataType ==
'camera':
945 elif dataType ==
'raw':
948 elif dataType ==
'bias':
951 elif dataType ==
'dark':
954 elif dataType ==
'flat':
957 elif dataType ==
'fringe':
960 elif dataType ==
'defects':
963 elif dataType ==
'bfKernel':
966 elif dataType ==
'linearizer':
968 elif dataType ==
'crosstalkSources':
971 raise RuntimeError(
"ISR DataRefMock cannot return %s.", dataType)
973 def put(self, exposure, filename):
974 r"""Write an exposure to a FITS file. 978 exposure : `lsst.afw.image.Exposure` 979 Image data to write out. 981 Base name of the output file. 983 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)