LSST Applications 27.0.0,g0265f82a02+469cd937ee,g02d81e74bb+21ad69e7e1,g1470d8bcf6+cbe83ee85a,g2079a07aa2+e67c6346a6,g212a7c68fe+04a9158687,g2305ad1205+94392ce272,g295015adf3+81dd352a9d,g2bbee38e9b+469cd937ee,g337abbeb29+469cd937ee,g3939d97d7f+72a9f7b576,g487adcacf7+71499e7cba,g50ff169b8f+5929b3527e,g52b1c1532d+a6fc98d2e7,g591dd9f2cf+df404f777f,g5a732f18d5+be83d3ecdb,g64a986408d+21ad69e7e1,g858d7b2824+21ad69e7e1,g8a8a8dda67+a6fc98d2e7,g99cad8db69+f62e5b0af5,g9ddcbc5298+d4bad12328,ga1e77700b3+9c366c4306,ga8c6da7877+71e4819109,gb0e22166c9+25ba2f69a1,gb6a65358fc+469cd937ee,gbb8dafda3b+69d3c0e320,gc07e1c2157+a98bf949bb,gc120e1dc64+615ec43309,gc28159a63d+469cd937ee,gcf0d15dbbd+72a9f7b576,gdaeeff99f8+a38ce5ea23,ge6526c86ff+3a7c1ac5f1,ge79ae78c31+469cd937ee,gee10cc3b42+a6fc98d2e7,gf1cff7945b+21ad69e7e1,gfbcc870c63+9a11dc8c8f
LSST Data Management Base Package
Loading...
Searching...
No Matches
isrMockLSST.py
Go to the documentation of this file.
1# This file is part of ip_isr.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["IsrMockLSSTConfig", "IsrMockLSST", "RawMockLSST",
23 "CalibratedRawMockLSST", "ReferenceMockLSST",
24 "BiasMockLSST", "DarkMockLSST", "FlatMockLSST", "FringeMockLSST",
25 "BfKernelMockLSST", "DefectMockLSST", "CrosstalkCoeffMockLSST",
26 "TransmissionMockLSST"]
27import numpy as np
28
29import lsst.geom as geom
30import lsst.pex.config as pexConfig
31from .crosstalk import CrosstalkCalib
32from .isrMock import IsrMockConfig, IsrMock
33
34
36 """Configuration parameters for isrMockLSST.
37 """
38 # Detector parameters and "Exposure" parameters,
39 # mostly inherited from IsrMockConfig.
40 isLsstLike = pexConfig.Field(
41 dtype=bool,
42 default=True,
43 doc="If True, products have one raw image per amplifier, otherwise, one raw image per detector.",
44 )
45 # Signal parameters.
46 # Most of them are inherited from isrMockConfig but we update
47 # some to LSSTcam expected values.
48 # TODO: DM-42880 Update values to what is expected in LSSTCam.
49 biasLevel = pexConfig.Field(
50 dtype=float,
51 default=30000.0,
52 doc="Background contribution to be generated from the bias offset in ADU.",
53 )
54 # Inclusion parameters are inherited from isrMock.
55 doAddParallelOverscan = pexConfig.Field(
56 dtype=bool,
57 default=True,
58 doc="Add overscan ramp to parallel overscan and data regions.",
59 )
60 doAddSerialOverscan = pexConfig.Field(
61 dtype=bool,
62 default=True,
63 doc="Add overscan ramp to serial overscan and data regions.",
64 )
65 doApplyGain = pexConfig.Field(
66 dtype=bool,
67 default=True,
68 doc="Add gain to data.",
69 )
70
71
73 """Class to generate consistent mock images for ISR testing.
74
75 ISR testing currently relies on one-off fake images that do not
76 accurately mimic the full set of detector effects. This class
77 uses the test camera/detector/amplifier structure defined in
78 `lsst.afw.cameraGeom.testUtils` to avoid making the test data
79 dependent on any of the actual obs package formats.
80 """
81 ConfigClass = IsrMockLSSTConfig
82 _DefaultName = "isrMockLSST"
83
84 def __init__(self, **kwargs):
85 # cross-talk coeffs, bf kernel are defined in the parent class.
86 super().__init__(**kwargs)
87
88 def run(self):
89 """Generate a mock ISR product following LSSTCam ISR, and return it.
90
91 Returns
92 -------
93 image : `lsst.afw.image.Exposure`
94 Simulated ISR image with signals added.
95 dataProduct :
96 Simulated ISR data products.
97 None :
98 Returned if no valid configuration was found.
99
100 Raises
101 ------
102 RuntimeError
103 Raised if both doGenerateImage and doGenerateData are specified.
104 """
105 if self.config.doGenerateImage and self.config.doGenerateData:
106 raise RuntimeError("Only one of doGenerateImage and doGenerateData may be specified.")
107 elif self.config.doGenerateImage:
108 return self.makeImagemakeImage()
109 elif self.config.doGenerateData:
110 return self.makeData()
111 else:
112 return None
113
114 def makeImage(self):
115 """Generate a simulated ISR LSST image.
116
117 Returns
118 -------
119 exposure : `lsst.afw.image.Exposure` or `dict`
120 Simulated ISR image data.
121
122 Notes
123 -----
124 This method constructs a "raw" data image.
125 """
126 exposure = self.getExposure()
127
128 # We introduce effects as they happen from a source to the signal,
129 # so the effects go from electrons to ADU.
130 # The ISR steps will then correct these effects in the reverse order.
131 for idx, amp in enumerate(exposure.getDetector()):
132
133 # Get image bbox and data
134 bbox = None
135 if self.config.isTrimmed:
136 bbox = amp.getBBox()
137 else:
138 bbox = amp.getRawDataBBox().shiftedBy(amp.getRawXYOffset())
139
140 ampData = exposure.image[bbox]
141
142 # Sky effects in e-
143 if self.config.doAddSky:
144 self.amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
145
146 if self.config.doAddSource:
147 for sourceAmp, sourceFlux, sourceX, sourceY in zip(self.config.sourceAmp,
148 self.config.sourceFlux,
149 self.config.sourceX,
150 self.config.sourceY):
151 if idx == sourceAmp:
152 self.amplifierAddSource(ampData, sourceFlux, sourceX, sourceY)
153
154 # Other effects in e-
155 if self.config.doAddFringe:
156 self.amplifierAddFringe(amp, ampData, np.array(self.config.fringeScale),
157 x0=np.array(self.config.fringeX0),
158 y0=np.array(self.config.fringeY0))
159
160 if self.config.doAddFlat:
161 if ampData.getArray().sum() == 0.0:
162 self.amplifierAddNoise(ampData, 1.0, 0.0)
163 u0 = exposure.getDimensions().getX()
164 v0 = exposure.getDimensions().getY()
165 self.amplifierMultiplyFlat(amp, ampData, self.config.flatDrop, u0=u0, v0=v0)
166
167 # ISR effects
168 # 1. Add dark in e- (different from isrMock which does it in ADU)
169 if self.config.doAddDark:
170 self.amplifierAddNoise(ampData,
171 self.config.darkRate * self.config.darkTime,
172 np.sqrt(self.config.darkRate * self.config.darkTime))
173
174 # 2. Gain normalize (from e- to ADU)
175 # TODO: DM-43601 gain from PTC per amplifier
176 # TODO: DM-36639 gain with temperature dependence
177 if self.config.doApplyGain:
178 self.applyGain(ampData, self.config.gain)
179
180 # 3. Add read noise with or without a bias level
181 # to the image region in ADU.
182 self.amplifierAddNoise(ampData, self.config.biasLevel if self.config.doAddBias else 0.0,
183 self.config.readNoise / self.config.gain)
184
185 # 4. Apply cross-talk in ADU
186 if self.config.doAddCrosstalk:
187 ctCalib = CrosstalkCalib()
188 for idxS, ampS in enumerate(exposure.getDetector()):
189 for idxT, ampT in enumerate(exposure.getDetector()):
190 ampDataT = exposure.image[ampT.getBBox() if self.config.isTrimmed
191 else ampT.getRawDataBBox().shiftedBy(ampT.getRawXYOffset())]
192 outAmp = ctCalib.extractAmp(exposure.getImage(), ampS, ampT,
193 isTrimmed=self.config.isTrimmed)
194 self.amplifierAddCT(outAmp, ampDataT, self.crosstalkCoeffs[idxS][idxT])
195
196 # We now apply parallel and serial overscans
197 for amp in exposure.getDetector():
198 # Get image bbox and data
199 bbox = None
200 if self.config.isTrimmed:
201 bbox = amp.getBBox()
202 else:
203 bbox = amp.getRawDataBBox().shiftedBy(amp.getRawXYOffset())
204 ampData = exposure.image[bbox]
205
206 # Get overscan bbox and data
207 if not self.config.isTrimmed:
208 parallelOscanBBox = amp.getRawParallelOverscanBBox().shiftedBy(amp.getRawXYOffset())
209 parallelOscanData = exposure.image[parallelOscanBBox]
210
211 serialOscanBBox = amp.getRawSerialOverscanBBox().shiftedBy(amp.getRawXYOffset())
212
213 # 5. Apply parallel overscan in ADU
214 if self.config.doAddParallelOverscan:
215 if not self.config.isTrimmed:
216 # Add read noise with or without a bias level
217 # to the parallel overscan region.
218 self.amplifierAddNoise(parallelOscanData, self.config.biasLevel
219 if self.config.doAddBias else 0.0,
220 self.config.readNoise / self.config.gain)
221 # Apply gradient along the Y axis
222 # to the parallel overscan region.
223 self.amplifierAddYGradient(parallelOscanData, -1.0 * self.config.overscanScale,
224 1.0 * self.config.overscanScale)
225
226 # Apply gradient along the Y axis to the image region
227 self.amplifierAddYGradient(ampData, -1.0 * self.config.overscanScale,
228 1.0 * self.config.overscanScale)
229
230 # 6. Add Parallel overscan xtalk.
231 # TODO: DM-43286
232
233 if self.config.doAddSerialOverscan:
234 if not self.config.isTrimmed:
235 # We grow the image to the parallel overscan region
236 # (we do this instead of using the whole raw region
237 # in case there are prescan regions)
238 grownImageBBox = bbox.expandedTo(parallelOscanBBox)
239 # Now we grow the serial overscan region
240 # to include the corners
241 serialOscanBBox = geom.Box2I(
242 geom.Point2I(serialOscanBBox.getMinX(),
243 grownImageBBox.getMinY()),
244 geom.Extent2I(serialOscanBBox.getWidth(),
245 grownImageBBox.getHeight()),
246 )
247 serialOscanData = exposure.image[serialOscanBBox]
248
249 # Add read noise with or without a bias level
250 # to the serial overscan region.
251 self.amplifierAddNoise(serialOscanData, self.config.biasLevel
252 if self.config.doAddBias else 0.0,
253 self.config.readNoise / self.config.gain)
254
255 # 7. Apply serial overscan in ADU
256 # Apply gradient along the X axis to both overscan regions.
257 self.amplifierAddXGradient(serialOscanData, -1.0 * self.config.overscanScale,
258 1.0 * self.config.overscanScale)
259 self.amplifierAddXGradient(parallelOscanData, -1.0 * self.config.overscanScale,
260 1.0 * self.config.overscanScale)
261
262 # Apply gradient along the X axis to the image region.
263 self.amplifierAddXGradient(ampData, -1.0 * self.config.overscanScale,
264 1.0 * self.config.overscanScale)
265
266 if self.config.doGenerateAmpDict:
267 expDict = dict()
268 for amp in exposure.getDetector():
269 expDict[amp.getName()] = exposure
270 return expDict
271 else:
272 return exposure
273
274 def applyGain(self, ampData, gain):
275 """Apply gain to the amplifier's data.
276 This method divides the data by the gain
277 because the mocks need to convert the data in electron to ADU,
278 so it does the inverse operation to applyGains in isrFunctions.
279
280 Parameters
281 ----------
282 ampData : `lsst.afw.image.ImageF`
283 Amplifier image to operate on.
284 gain : `float`
285 Gain value in e^-/DN.
286 """
287 ampArr = ampData.array
288 ampArr[:] = ampArr[:] / gain
289
290 def amplifierAddXGradient(self, ampData, start, end):
291 """Add a x-axis linear gradient to an amplifier's image data.
292
293 This method operates in the amplifier coordinate frame.
294
295 Parameters
296 ----------
297 ampData : `lsst.afw.image.ImageF`
298 Amplifier image to operate on.
299 start : `float`
300 Start value of the gradient (at y=0).
301 end : `float`
302 End value of the gradient (at y=ymax).
303 """
304 nPixX = ampData.getDimensions().getX()
305 ampArr = ampData.array
306 ampArr[:] = ampArr[:] + (np.interp(range(nPixX), (0, nPixX - 1), (start, end)).reshape(1, nPixX)
307 + np.zeros(ampData.getDimensions()).transpose())
308
309
311 """Generate a raw exposure suitable for ISR.
312 """
313 def __init__(self, **kwargs):
314 super().__init__(**kwargs)
315 self.config.isTrimmed = False
316 self.config.doGenerateImage = True
317 self.config.doGenerateAmpDict = False
318
319 # Add astro effects
320 self.config.doAddSky = True
321 self.config.doAddSource = True
322
323 # Add optical effects
324 self.config.doAddFringe = True
325
326 # Add instru effects
327 self.config.doAddParallelOverscan = True
328 self.config.doAddSerialOverscan = True
329 self.config.doAddCrosstalk = False
330 self.config.doAddBias = True
331 self.config.doAddDark = True
332
333 self.config.doAddFlat = True
334
335
337 """Generate a trimmed raw exposure.
338 """
339 def __init__(self, **kwargs):
340 super().__init__(**kwargs)
341 self.config.isTrimmed = True
342 self.config.doAddParallelOverscan = False
343 self.config.doAddSerialOverscan = False
344
345
347 """Generate a trimmed raw exposure.
348 """
349 def __init__(self, **kwargs):
350 super().__init__(**kwargs)
351 self.config.isTrimmed = True
352 self.config.doGenerateImage = True
353
354 self.config.doAddSky = True
355 self.config.doAddSource = True
356
357 self.config.doAddFringe = True
358
359 self.config.doAddParallelOverscan = False
360 self.config.doAddSerialOverscan = False
361 self.config.doAddCrosstalk = False
362 self.config.doAddBias = False
363 self.config.doAddDark = False
364 self.config.doApplyGain = False
365 self.config.doAddFlat = False
366
367 self.config.biasLevel = 0.0
368 # Assume combined calibrations are made with 16 inputs.
369 self.config.readNoise *= 0.25
370
371
373 """Parent class for those that make reference calibrations.
374 """
375 def __init__(self, **kwargs):
376 super().__init__(**kwargs)
377 self.config.isTrimmed = True
378 self.config.doGenerateImage = True
379
380 self.config.doAddSky = False
381 self.config.doAddSource = False
382
383 self.config.doAddFringe = False
384
385 self.config.doAddParallelOverscan = False
386 self.config.doAddSerialOverscan = False
387 self.config.doAddCrosstalk = False
388 self.config.doAddBias = False
389 self.config.doAddDark = False
390 self.config.doApplyGain = False
391 self.config.doAddFlat = False
392
393
394# Classes to generate calibration products mocks.
396 """Simulated reference dark calibration.
397 """
398 def __init__(self, **kwargs):
399 super().__init__(**kwargs)
400 self.config.doAddDark = True
401 self.config.darkTime = 1.0
402
403
405 """Simulated combined bias calibration.
406 """
407 def __init__(self, **kwargs):
408 super().__init__(**kwargs)
409 # A combined bias has mean 0
410 # so we set its bias level to 0.
411 # This is equivalent to doAddBias = False
412 # but we do the following instead to be consistent
413 # with any other bias products we might want to produce.
414 self.config.doAddBias = True
415 self.config.biasLevel - 0.0
416 self.config.doApplyGain = True
417 # Assume combined calibrations are made with 16 inputs.
418 self.config.readNoise = 10.0*0.25
419
420
422 """Simulated reference flat calibration.
423 """
424 def __init__(self, **kwargs):
425 super().__init__(**kwargs)
426 self.config.doAddFlat = True
427
428
430 """Simulated reference fringe calibration.
431 """
432 def __init__(self, **kwargs):
433 super().__init__(**kwargs)
434 self.config.doAddFringe = True
435
436
438 """Simulated brighter-fatter kernel.
439 """
440 def __init__(self, **kwargs):
441 super().__init__(**kwargs)
442 self.config.doGenerateImage = False
443 self.config.doGenerateData = True
444
445 # calibration products configs
446 self.config.doBrighterFatter = True
447 self.config.doDefects = False
448 self.config.doCrosstalkCoeffs = False
449 self.config.doTransmissionCurve = False
450
451
453 """Simulated defect list.
454 """
455 def __init__(self, **kwargs):
456 super().__init__(**kwargs)
457 self.config.doGenerateImage = False
458 self.config.doGenerateData = True
459
460 self.config.doBrighterFatter = False
461 self.config.doDefects = True
462 self.config.doCrosstalkCoeffs = False
463 self.config.doTransmissionCurve = False
464
465
467 """Simulated crosstalk coefficient matrix.
468 """
469 def __init__(self, **kwargs):
470 super().__init__(**kwargs)
471 self.config.doGenerateImage = False
472 self.config.doGenerateData = True
473
474 self.config.doBrighterFatter = False
475 self.config.doDefects = False
476 self.config.doCrosstalkCoeffs = True
477 self.config.doTransmissionCurve = False
478
479
481 """Simulated transmission curve.
482 """
483 def __init__(self, **kwargs):
484 super().__init__(**kwargs)
485 self.config.doGenerateImage = False
486 self.config.doGenerateData = True
487
488 self.config.doBrighterFatter = False
489 self.config.doDefects = False
490 self.config.doCrosstalkCoeffs = False
491 self.config.doTransmissionCurve = True
An integer coordinate rectangle.
Definition Box.h:55
amplifierAddSource(self, ampData, scale, x0, y0)
Definition isrMock.py:710
amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0)
Definition isrMock.py:785
amplifierAddYGradient(self, ampData, start, end)
Definition isrMock.py:691
amplifierAddNoise(self, ampData, mean, sigma)
Definition isrMock.py:673
amplifierAddCT(self, ampDataSource, ampDataTarget, scale)
Definition isrMock.py:731
amplifierAddFringe(self, amp, ampData, scale, x0=100, y0=0)
Definition isrMock.py:753
amplifierAddXGradient(self, ampData, start, end)