34 from .crosstalk
import CrosstalkCalib
35 from .defects
import Defects
37 __all__ = [
"IsrMockConfig",
"IsrMock",
"RawMock",
"TrimmedRawMock",
"RawDictMock",
38 "CalibratedRawMock",
"MasterMock",
39 "BiasMock",
"DarkMock",
"FlatMock",
"FringeMock",
"UntrimmedFringeMock",
40 "BfKernelMock",
"DefectMock",
"CrosstalkCoeffMock",
"TransmissionMock",
45 """Configuration parameters for isrMock.
47 These parameters produce generic fixed position signals from
48 various sources, and combine them in a way that matches how those
49 signals are combined to create real data. The camera used is the
50 test camera defined by the afwUtils code.
53 isLsstLike = pexConfig.Field(
56 doc=
"If True, products have one raw image per amplifier, otherwise, one raw image per detector.",
58 plateScale = pexConfig.Field(
61 doc=
"Plate scale used in constructing mock camera.",
63 radialDistortion = pexConfig.Field(
66 doc=
"Radial distortion term used in constructing mock camera.",
68 isTrimmed = pexConfig.Field(
71 doc=
"If True, amplifiers have been trimmed and mosaicked to remove regions outside the data BBox.",
73 detectorIndex = pexConfig.Field(
76 doc=
"Index for the detector to use. The default value uses a standard 2x4 array of amps.",
78 rngSeed = pexConfig.Field(
81 doc=
"Seed for random number generator used to add noise.",
84 gain = pexConfig.Field(
87 doc=
"Gain for simulated data in e^-/DN.",
89 readNoise = pexConfig.Field(
92 doc=
"Read noise of the detector in e-.",
94 expTime = pexConfig.Field(
97 doc=
"Exposure time for simulated data.",
101 skyLevel = pexConfig.Field(
104 doc=
"Background contribution to be generated from 'the sky' in DN.",
106 sourceFlux = pexConfig.ListField(
109 doc=
"Peak flux level (in DN) of simulated 'astronomical sources'.",
111 sourceAmp = pexConfig.ListField(
114 doc=
"Amplifier to place simulated 'astronomical sources'.",
116 sourceX = pexConfig.ListField(
119 doc=
"Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
121 sourceY = pexConfig.ListField(
124 doc=
"Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
126 overscanScale = pexConfig.Field(
129 doc=
"Amplitude (in DN) of the ramp function to add to overscan data.",
131 biasLevel = pexConfig.Field(
134 doc=
"Background contribution to be generated from the bias offset in DN.",
136 darkRate = pexConfig.Field(
139 doc=
"Background level contribution (in e-/s) to be generated from dark current.",
141 darkTime = pexConfig.Field(
144 doc=
"Exposure time for the dark current contribution.",
146 flatDrop = pexConfig.Field(
149 doc=
"Fractional flux drop due to flat from center to edge of detector along x-axis.",
151 fringeScale = pexConfig.ListField(
154 doc=
"Peak fluxes for the components of the fringe ripple in DN.",
156 fringeX0 = pexConfig.ListField(
159 doc=
"Center position for the fringe ripples.",
161 fringeY0 = pexConfig.ListField(
164 doc=
"Center position for the fringe ripples.",
168 doAddSky = pexConfig.Field(
171 doc=
"Apply 'sky' signal to output image.",
173 doAddSource = pexConfig.Field(
176 doc=
"Add simulated source to output image.",
178 doAddCrosstalk = pexConfig.Field(
181 doc=
"Apply simulated crosstalk to output image. This cannot be corrected by ISR, "
182 "as detector.hasCrosstalk()==False.",
184 doAddOverscan = pexConfig.Field(
187 doc=
"If untrimmed, add overscan ramp to overscan and data regions.",
189 doAddBias = pexConfig.Field(
192 doc=
"Add bias signal to data.",
194 doAddDark = pexConfig.Field(
197 doc=
"Add dark signal to data.",
199 doAddFlat = pexConfig.Field(
202 doc=
"Add flat signal to data.",
204 doAddFringe = pexConfig.Field(
207 doc=
"Add fringe signal to data.",
211 doTransmissionCurve = pexConfig.Field(
214 doc=
"Return a simulated transmission curve.",
216 doDefects = pexConfig.Field(
219 doc=
"Return a simulated defect list.",
221 doBrighterFatter = pexConfig.Field(
224 doc=
"Return a simulated brighter-fatter kernel.",
226 doCrosstalkCoeffs = pexConfig.Field(
229 doc=
"Return the matrix of crosstalk coefficients.",
231 doDataRef = pexConfig.Field(
234 doc=
"Return a simulated gen2 butler dataRef.",
236 doGenerateImage = pexConfig.Field(
239 doc=
"Return the generated output image if True.",
241 doGenerateData = pexConfig.Field(
244 doc=
"Return a non-image data structure if True.",
246 doGenerateAmpDict = pexConfig.Field(
249 doc=
"Return a dict of exposure amplifiers instead of an afwImage.Exposure.",
254 """Class to generate consistent mock images for ISR testing.
256 ISR testing currently relies on one-off fake images that do not
257 accurately mimic the full set of detector effects. This class
258 uses the test camera/detector/amplifier structure defined in
259 `lsst.afw.cameraGeom.testUtils` to avoid making the test data
260 dependent on any of the actual obs package formats.
262 ConfigClass = IsrMockConfig
263 _DefaultName =
"isrMock"
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
284 """Generate a mock ISR product, and return it.
288 image : `lsst.afw.image.Exposure`
289 Simulated ISR image with signals added.
291 Simulated ISR data products.
293 Returned if no valid configuration was found.
298 Raised if both doGenerateImage and doGenerateData are specified.
300 if self.config.doGenerateImage
and self.config.doGenerateData:
301 raise RuntimeError(
"Only one of doGenerateImage and doGenerateData may be specified.")
302 elif self.config.doGenerateImage:
304 elif self.config.doGenerateData:
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:
338 """Generate a simple Gaussian brighter-fatter kernel.
342 kernel : `numpy.ndarray`
343 Simulated brighter-fatter kernel.
348 """Generate a simple single-entry defect list.
352 defectList : `lsst.meas.algorithms.Defects`
353 Simulated defect list
359 """Generate the simulated crosstalk coefficients.
363 coeffs : `numpy.ndarray`
364 Simulated crosstalk coefficients.
370 """Generate a simulated flat transmission curve.
374 transmission : `lsst.afw.image.TransmissionCurve`
375 Simulated transmission curve.
378 return afwImage.TransmissionCurve.makeIdentity()
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:
391 * Generating a simulated sky with noise
392 * Adding a single Gaussian "star"
393 * Adding the fringe signal
394 * Multiplying the frame by the simulated flat
395 * Adding dark current (and noise)
396 * Adding a bias offset (and noise)
397 * Adding an overscan gradient parallel to the pixel y-axis
398 * Simulating crosstalk by adding a scaled version of each
399 amplifier to each other amplifier.
401 The exposure with image data constructed this way is in one of
403 * A single image, with overscan and prescan regions retained
404 * A single image, with overscan and prescan regions trimmed
405 * A `dict`, containing the amplifer data indexed by the
408 The nonlinearity, CTE, and brighter fatter are currently not
411 Note that this method generates an image in the reverse
412 direction as the ISR processing, as the output image here has
413 had a series of instrument effects added to an idealized
418 for idx, amp
in enumerate(exposure.getDetector()):
420 if self.config.isTrimmed
is True:
423 bbox = amp.getRawDataBBox()
425 ampData = exposure.image[bbox]
427 if self.config.doAddSky
is True:
428 self.
amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
430 if self.config.doAddSource
is True:
431 for sourceAmp, sourceFlux, sourceX, sourceY
in zip(self.config.sourceAmp,
432 self.config.sourceFlux,
434 self.config.sourceY):
438 if self.config.doAddFringe
is True:
440 x0=np.array(self.config.fringeX0),
441 y0=np.array(self.config.fringeY0))
443 if self.config.doAddFlat
is True:
444 if ampData.getArray().sum() == 0.0:
446 u0 = exposure.getDimensions().getX()
447 v0 = exposure.getDimensions().getY()
450 if self.config.doAddDark
is True:
452 self.config.darkRate * self.config.darkTime / self.config.gain,
453 np.sqrt(self.config.darkRate
454 * self.config.darkTime / self.config.gain))
456 if self.config.doAddCrosstalk
is True:
458 for idxS, ampS
in enumerate(exposure.getDetector()):
459 for idxT, ampT
in enumerate(exposure.getDetector()):
460 ampDataT = exposure.image[ampT.getBBox()
461 if self.config.isTrimmed
else ampT.getRawDataBBox()]
462 outAmp = ctCalib.extractAmp(exposure.getImage(), ampS, ampT,
463 isTrimmed=self.config.isTrimmed)
466 for amp
in exposure.getDetector():
468 if self.config.isTrimmed
is True:
471 bbox = amp.getRawDataBBox()
473 ampData = exposure.image[bbox]
475 if self.config.doAddBias
is True:
477 self.config.readNoise / self.config.gain)
479 if self.config.doAddOverscan
is True:
480 oscanBBox = amp.getRawHorizontalOverscanBBox()
481 oscanData = exposure.image[oscanBBox]
483 self.config.readNoise / self.config.gain)
486 1.0 * self.config.overscanScale)
488 1.0 * self.config.overscanScale)
490 if self.config.doGenerateAmpDict
is True:
492 for amp
in exposure.getDetector():
493 expDict[amp.getName()] = exposure
500 """Construct a test camera object.
504 camera : `lsst.afw.cameraGeom.camera`
507 cameraWrapper = afwTestUtils.CameraWrapper(
508 plateScale=self.config.plateScale,
509 radialDistortion=self.config.radialDistortion,
510 isLsstLike=self.config.isLsstLike,
512 camera = cameraWrapper.camera
516 """Construct a test exposure.
518 The test exposure has a simple WCS set, as well as a list of
519 unlikely header keywords that can be removed during ISR
520 processing to exercise that code.
524 exposure : `lsst.afw.exposure.Exposure`
525 Construct exposure containing masked image of the
529 detector = camera[self.config.detectorIndex]
530 image = afwUtils.makeImageFromCcd(detector,
531 isTrimmed=self.config.isTrimmed,
535 imageFactory=afwImage.ImageF)
537 var = afwImage.ImageF(image.getDimensions())
543 exposure.setDetector(detector)
544 exposure.setWcs(self.
getWcs())
546 visitInfo =
afwImage.VisitInfo(exposureTime=self.config.expTime, darkTime=self.config.darkTime)
547 exposure.getInfo().setVisitInfo(visitInfo)
549 metadata = exposure.getMetadata()
550 metadata.add(
"SHEEP", 7.3,
"number of sheep on farm")
551 metadata.add(
"MONKEYS", 155,
"monkeys per tree")
552 metadata.add(
"VAMPIRES", 4,
"How scary are vampires.")
554 ccd = exposure.getDetector()
555 newCcd = ccd.rebuild()
558 newAmp = amp.rebuild()
559 newAmp.setLinearityCoeffs((0., 1., 0., 0.))
560 newAmp.setLinearityType(
"Polynomial")
561 newAmp.setGain(self.config.gain)
562 newAmp.setSuspectLevel(25000.0)
563 newAmp.setSaturation(32000.0)
564 newCcd.append(newAmp)
565 exposure.setDetector(newCcd.finish())
567 exposure.image.array[:] = np.zeros(exposure.getImage().getDimensions()).transpose()
568 exposure.mask.array[:] = np.zeros(exposure.getMask().getDimensions()).transpose()
569 exposure.variance.array[:] = np.zeros(exposure.getVariance().getDimensions()).transpose()
574 """Construct a dummy WCS object.
576 Taken from the deprecated ip_isr/examples/exampleUtils.py.
578 This is not guaranteed, given the distortion and pixel scale
579 listed in the afwTestUtils camera definition.
583 wcs : `lsst.afw.geom.SkyWcs`
591 """Convert between a local amplifier coordinate and the full
596 ampData : `lsst.afw.image.ImageF`
597 Amplifier image to use for conversions.
599 X-coordinate of the point to transform.
601 Y-coordinate of the point to transform.
606 Transformed x-coordinate.
608 Transformed y-coordinate.
612 The output is transposed intentionally here, to match the
613 internal transpose between numpy and afw.image coordinates.
615 u = x + ampData.getBBox().getBeginX()
616 v = y + ampData.getBBox().getBeginY()
622 """Add Gaussian noise to an amplifier's image data.
624 This method operates in the amplifier coordinate frame.
628 ampData : `lsst.afw.image.ImageF`
629 Amplifier image to operate on.
631 Mean value of the Gaussian noise.
633 Sigma of the Gaussian noise.
635 ampArr = ampData.array
636 ampArr[:] = ampArr[:] + self.
rng.normal(mean, sigma,
637 size=ampData.getDimensions()).transpose()
640 """Add a y-axis linear gradient to an amplifier's image data.
642 This method operates in the amplifier coordinate frame.
646 ampData : `lsst.afw.image.ImageF`
647 Amplifier image to operate on.
649 Start value of the gradient (at y=0).
651 End value of the gradient (at y=ymax).
653 nPixY = ampData.getDimensions().getY()
654 ampArr = ampData.array
655 ampArr[:] = ampArr[:] + (np.interp(range(nPixY), (0, nPixY - 1), (start, end)).reshape(nPixY, 1)
656 + np.zeros(ampData.getDimensions()).transpose())
659 """Add a single Gaussian source to an amplifier.
661 This method operates in the amplifier coordinate frame.
665 ampData : `lsst.afw.image.ImageF`
666 Amplifier image to operate on.
668 Peak flux of the source to add.
670 X-coordinate of the source peak.
672 Y-coordinate of the source peak.
674 for x
in range(0, ampData.getDimensions().getX()):
675 for y
in range(0, ampData.getDimensions().getY()):
676 ampData.array[y][x] = (ampData.array[y][x]
677 + scale * np.exp(-0.5 * ((x - x0)**2 + (y - y0)**2) / 3.0**2))
680 """Add a scaled copy of an amplifier to another, simulating crosstalk.
682 This method operates in the amplifier coordinate frame.
686 ampDataSource : `lsst.afw.image.ImageF`
687 Amplifier image to add scaled copy from.
688 ampDataTarget : `lsst.afw.image.ImageF`
689 Amplifier image to add scaled copy to.
691 Flux scale of the copy to add to the target.
695 This simulates simple crosstalk between amplifiers.
697 ampDataTarget.array[:] = (ampDataTarget.array[:]
698 + scale * ampDataSource.array[:])
702 """Add a fringe-like ripple pattern to an amplifier's image data.
706 amp : `~lsst.afw.ampInfo.AmpInfoRecord`
707 Amplifier to operate on. Needed for amp<->exp coordinate transforms.
708 ampData : `lsst.afw.image.ImageF`
709 Amplifier image to operate on.
710 scale : `numpy.array` or `float`
711 Peak intensity scaling for the ripple.
712 x0 : `numpy.array` or `float`, optional
714 y0 : `numpy.array` or `float`, optional
719 This uses an offset sinc function to generate a ripple
720 pattern. True fringes have much finer structure, but this
721 pattern should be visually identifiable. The (x, y)
722 coordinates are in the frame of the amplifier, and (u, v) in
723 the frame of the full trimmed image.
725 for x
in range(0, ampData.getDimensions().getX()):
726 for y
in range(0, ampData.getDimensions().getY()):
728 ampData.getArray()[y][x] = np.sum((ampData.getArray()[y][x]
729 + scale * np.sinc(((u - x0) / 50)**2
730 + ((v - y0) / 50)**2)))
733 """Multiply an amplifier's image data by a flat-like pattern.
737 amp : `lsst.afw.ampInfo.AmpInfoRecord`
738 Amplifier to operate on. Needed for amp<->exp coordinate transforms.
739 ampData : `lsst.afw.image.ImageF`
740 Amplifier image to operate on.
742 Fractional drop from center to edge of detector along x-axis.
744 Peak location in detector coordinates.
746 Peak location in detector coordinates.
750 This uses a 2-d Gaussian to simulate an illumination pattern
751 that falls off towards the edge of the detector. The (x, y)
752 coordinates are in the frame of the amplifier, and (u, v) in
753 the frame of the full trimmed image.
756 raise RuntimeError(
"Flat fractional drop cannot be greater than 1.0")
758 sigma = u0 / np.sqrt(-2.0 * np.log(fracDrop))
760 for x
in range(0, ampData.getDimensions().getX()):
761 for y
in range(0, ampData.getDimensions().getY()):
763 f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
764 ampData.array[y][x] = (ampData.array[y][x] * f)
768 """Generate a raw exposure suitable for ISR.
772 self.config.isTrimmed =
False
773 self.config.doGenerateImage =
True
774 self.config.doGenerateAmpDict =
False
775 self.config.doAddOverscan =
True
776 self.config.doAddSky =
True
777 self.config.doAddSource =
True
778 self.config.doAddCrosstalk =
False
779 self.config.doAddBias =
True
780 self.config.doAddDark =
True
784 """Generate a trimmed raw exposure.
788 self.config.isTrimmed =
True
789 self.config.doAddOverscan =
False
793 """Generate a trimmed raw exposure.
797 self.config.isTrimmed =
True
798 self.config.doGenerateImage =
True
799 self.config.doAddOverscan =
False
800 self.config.doAddSky =
True
801 self.config.doAddSource =
True
802 self.config.doAddCrosstalk =
False
804 self.config.doAddBias =
False
805 self.config.doAddDark =
False
806 self.config.doAddFlat =
False
807 self.config.doAddFringe =
True
809 self.config.biasLevel = 0.0
810 self.config.readNoise = 10.0
814 """Generate a raw exposure dict suitable for ISR.
818 self.config.doGenerateAmpDict =
True
822 """Parent class for those that make master calibrations.
826 self.config.isTrimmed =
True
827 self.config.doGenerateImage =
True
828 self.config.doAddOverscan =
False
829 self.config.doAddSky =
False
830 self.config.doAddSource =
False
831 self.config.doAddCrosstalk =
False
833 self.config.doAddBias =
False
834 self.config.doAddDark =
False
835 self.config.doAddFlat =
False
836 self.config.doAddFringe =
False
840 """Simulated master bias calibration.
844 self.config.doAddBias =
True
845 self.config.readNoise = 10.0
849 """Simulated master dark calibration.
853 self.config.doAddDark =
True
854 self.config.darkTime = 1.0
858 """Simulated master flat calibration.
862 self.config.doAddFlat =
True
866 """Simulated master fringe calibration.
870 self.config.doAddFringe =
True
874 """Simulated untrimmed master fringe calibration.
878 self.config.isTrimmed =
False
882 """Simulated brighter-fatter kernel.
886 self.config.doGenerateImage =
False
887 self.config.doGenerateData =
True
888 self.config.doBrighterFatter =
True
889 self.config.doDefects =
False
890 self.config.doCrosstalkCoeffs =
False
891 self.config.doTransmissionCurve =
False
895 """Simulated defect list.
899 self.config.doGenerateImage =
False
900 self.config.doGenerateData =
True
901 self.config.doBrighterFatter =
False
902 self.config.doDefects =
True
903 self.config.doCrosstalkCoeffs =
False
904 self.config.doTransmissionCurve =
False
908 """Simulated crosstalk coefficient matrix.
912 self.config.doGenerateImage =
False
913 self.config.doGenerateData =
True
914 self.config.doBrighterFatter =
False
915 self.config.doDefects =
False
916 self.config.doCrosstalkCoeffs =
True
917 self.config.doTransmissionCurve =
False
921 """Simulated transmission curve.
925 self.config.doGenerateImage =
False
926 self.config.doGenerateData =
True
927 self.config.doBrighterFatter =
False
928 self.config.doDefects =
False
929 self.config.doCrosstalkCoeffs =
False
930 self.config.doTransmissionCurve =
True
934 """Simulated gen2 butler data ref.
936 Currently only supports get and put operations, which are most
937 likely to be called for data in ISR processing.
940 dataId =
"isrMock Fake Data"
948 if 'config' in kwargs.keys():
956 self.
config.doGenerateImage =
True
957 self.
config.doGenerateData =
False
962 self.
config.doGenerateImage =
False
963 self.
config.doGenerateData =
True
965 def get(self, dataType, **kwargs):
966 """Return an appropriate data product.
971 Type of data product to return.
975 mock : IsrMock.run() result
978 if "_filename" in dataType:
980 return tempfile.mktemp(),
"mock"
981 elif 'transmission_' in dataType:
984 elif dataType ==
'ccdExposureId':
987 elif dataType ==
'camera':
990 elif dataType ==
'raw':
993 elif dataType ==
'bias':
996 elif dataType ==
'dark':
999 elif dataType ==
'flat':
1002 elif dataType ==
'fringe':
1005 elif dataType ==
'defects':
1008 elif dataType ==
'bfKernel':
1011 elif dataType ==
'linearizer':
1013 elif dataType ==
'crosstalkSources':
1016 raise RuntimeError(
"ISR DataRefMock cannot return %s.", dataType)
1018 def put(self, exposure, filename):
1019 """Write an exposure to a FITS file.
1023 exposure : `lsst.afw.image.Exposure`
1024 Image data to write out.
1026 Base name of the output file.
1028 exposure.writeFits(filename+
".fits")
1032 """Simulated gen2 butler data ref.
1034 Currently only supports get and put operations, which are most
1035 likely to be called for data in ISR processing.
1038 dataId =
"isrMock Fake Data"
1046 if 'config' in kwargs.keys():
1050 self.
config.isTrimmed =
True
1051 self.
config.doAddFringe =
True
1052 self.
config.readNoise = 10.0
1054 def get(self, dataType, **kwargs):
1055 """Return an appropriate data product.
1060 Type of data product to return.
1064 mock : IsrMock.run() result
1067 if "_filename" in dataType:
1068 return tempfile.mktemp(),
"mock"
1069 elif 'transmission_' in dataType:
1071 elif dataType ==
'ccdExposureId':
1073 elif dataType ==
'camera':
1075 elif dataType ==
'raw':
1077 elif dataType ==
'bias':
1079 elif dataType ==
'dark':
1081 elif dataType ==
'flat':
1083 elif dataType ==
'fringe':
1085 configCopy = copy.deepcopy(self.
config)
1086 for scale, x, y
in zip(self.
config.fringeScale, self.
config.fringeX0, self.
config.fringeY0):
1087 configCopy.fringeScale = [1.0]
1088 configCopy.fringeX0 = [x]
1089 configCopy.fringeY0 = [y]
1092 elif dataType ==
'defects':
1094 elif dataType ==
'bfKernel':
1096 elif dataType ==
'linearizer':
1098 elif dataType ==
'crosstalkSources':
1103 def put(self, exposure, filename):
1104 """Write an exposure to a FITS file.
1108 exposure : `lsst.afw.image.Exposure`
1109 Image data to write out.
1111 Base name of the output file.
1113 exposure.writeFits(filename+
".fits")