LSST Applications g063fba187b+cac8b7c890,g0f08755f38+6aee506743,g1653933729+a8ce1bb630,g168dd56ebc+a8ce1bb630,g1a2382251a+b4475c5878,g1dcb35cd9c+8f9bc1652e,g20f6ffc8e0+6aee506743,g217e2c1bcf+73dee94bd0,g28da252d5a+1f19c529b9,g2bbee38e9b+3f2625acfc,g2bc492864f+3f2625acfc,g3156d2b45e+6e55a43351,g32e5bea42b+1bb94961c2,g347aa1857d+3f2625acfc,g35bb328faa+a8ce1bb630,g3a166c0a6a+3f2625acfc,g3e281a1b8c+c5dd892a6c,g3e8969e208+a8ce1bb630,g414038480c+5927e1bc1e,g41af890bb2+8a9e676b2a,g7af13505b9+809c143d88,g80478fca09+6ef8b1810f,g82479be7b0+f568feb641,g858d7b2824+6aee506743,g89c8672015+f4add4ffd5,g9125e01d80+a8ce1bb630,ga5288a1d22+2903d499ea,gb58c049af0+d64f4d3760,gc28159a63d+3f2625acfc,gcab2d0539d+b12535109e,gcf0d15dbbd+46a3f46ba9,gda6a2b7d83+46a3f46ba9,gdaeeff99f8+1711a396fd,ge79ae78c31+3f2625acfc,gef2f8181fd+0a71e47438,gf0baf85859+c1f95f4921,gfa517265be+6aee506743,gfa999e8aa5+17cd334064,w.2024.51
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"]
27
28import numpy as np
29import galsim
30
31import lsst.geom as geom
32import lsst.pex.config as pexConfig
33import lsst.afw.detection as afwDetection
34import lsst.afw.math as afwMath
35from lsst.afw.cameraGeom import ReadoutCorner
36from lsst.daf.base import PropertyList
37from .crosstalk import CrosstalkCalib
38from .isrMock import IsrMockConfig, IsrMock
39from .defects import Defects
40from .assembleCcdTask import AssembleCcdTask
41from .linearize import Linearizer
42from .brighterFatterKernel import BrighterFatterKernel
43from .deferredCharge import (
44 SegmentSimulator,
45 FloatingOutputAmplifier,
46 DeferredChargeCalib
47)
48
49
51 """Configuration parameters for isrMockLSST.
52 """
53 # Detector parameters and "Exposure" parameters,
54 # mostly inherited from IsrMockConfig.
55 isLsstLike = pexConfig.Field(
56 dtype=bool,
57 default=True,
58 doc="If True, products have one raw image per amplifier, otherwise, one raw image per detector.",
59 )
60 calibMode = pexConfig.Field(
61 dtype=bool,
62 default=False,
63 doc="Set to true to produce mock calibration products, e.g. combined bias, dark, flat, etc.",
64 )
65 doAdd2DBias = pexConfig.Field(
66 dtype=bool,
67 default=True,
68 doc="Add 2D bias residual frame to data.",
69 )
70 doAddBrightDefects = pexConfig.Field(
71 dtype=bool,
72 default=True,
73 doc="Add bright defects (bad column) to data.",
74 )
75 brightDefectLevel = pexConfig.Field(
76 dtype=float,
77 default=30000.0,
78 doc="Bright defect level (electron).",
79 )
80 doAddBadParallelOverscanColumn = pexConfig.Field(
81 dtype=bool,
82 default=True,
83 doc="Add a bad column to the parallel overscan.",
84 )
85 badParallelOverscanColumnLevel = pexConfig.Field(
86 dtype=float,
87 default=300000.,
88 doc="Bright parallel overscan column level (electron). Should be above saturation.",
89 )
90 doAddBadParallelOverscanColumnNeighbors = pexConfig.Field(
91 dtype=bool,
92 default=True,
93 doc="Add low-level bad columns next to parallel overscan bad column.",
94 )
95 badParallelOverscanColumnNeighborsLevel = pexConfig.Field(
96 dtype=float,
97 default=50.0,
98 doc="Bright parallel overscan column neighbors level (electron).",
99 )
100 doAddBrighterFatter = pexConfig.Field(
101 dtype=bool,
102 default=False,
103 doc="Add brighter fatter and/or diffusion effects to image.",
104 )
105 doAddDeferredCharge = pexConfig.Field(
106 dtype=bool,
107 default=False,
108 doc="Add serial CTI at the amp level?",
109 )
110 bfStrength = pexConfig.Field(
111 dtype=float,
112 default=2.0,
113 doc="The brighter fatter effect scaling parameter (cannot be zero)."
114 "Nominally = 1, but = 2 is more realistic."
115 )
116 nRecalc = pexConfig.Field(
117 dtype=int,
118 default=10000,
119 doc="Number of electrons to accumulate before recalculating pixel shapes.",
120 )
121 doAddClockInjectedOffset = pexConfig.Field(
122 dtype=bool,
123 default=True,
124 doc="Add clock-injected offset to data (on-chip bias level).",
125 )
126 clockInjectedOffsetLevel = pexConfig.Field(
127 dtype=float,
128 default=8500.0,
129 doc="Clock-injected offset (on-chip bias level), in electron.",
130 )
131 noise2DBias = pexConfig.Field(
132 dtype=float,
133 default=2.0,
134 doc="Noise (in electron) to generate a 2D bias residual frame.",
135 )
136 doAddDarkNoiseOnly = pexConfig.Field(
137 dtype=bool,
138 default=False,
139 doc="Add only dark current noise, for testing consistency.",
140 )
141 doAddParallelOverscanRamp = pexConfig.Field(
142 dtype=bool,
143 default=True,
144 doc="Add overscan ramp to parallel overscan and data regions.",
145 )
146 doAddSerialOverscanRamp = pexConfig.Field(
147 dtype=bool,
148 default=True,
149 doc="Add overscan ramp to serial overscan and data regions.",
150 )
151 doAddHighSignalNonlinearity = pexConfig.Field(
152 dtype=bool,
153 default=True,
154 doc="Add high signal non-linearity to overscan and data regions?",
155 )
156 doAddLowSignalNonlinearity = pexConfig.Field(
157 dtype=bool,
158 default=False,
159 doc="Add low signal non-linearity to overscan and data regions? (Not supported yet.",
160 )
161 highSignalNonlinearityThreshold = pexConfig.Field(
162 dtype=float,
163 default=40_000.,
164 doc="Threshold (in adu) for the non-linearity to be considered ``high signal``.",
165 )
166 doApplyGain = pexConfig.Field(
167 dtype=bool,
168 default=True,
169 doc="Add gain to data.",
170 )
171 doRoundAdu = pexConfig.Field(
172 dtype=bool,
173 default=True,
174 doc="Round adu values to nearest integer.",
175 )
176 gainDict = pexConfig.DictField(
177 keytype=str,
178 itemtype=float,
179 doc="Dictionary of amp name to gain; any amps not listed will use "
180 "config.gain as the value. Units are electron/adu.",
181 default={
182 "C:0,0": 1.65,
183 "C:0,1": 1.60,
184 "C:0,2": 1.55,
185 "C:0,3": 1.70,
186 "C:1,0": 1.75,
187 "C:1,1": 1.80,
188 "C:1,2": 1.85,
189 "C:1,3": 1.70,
190 },
191 )
192 assembleCcd = pexConfig.ConfigurableField(
193 target=AssembleCcdTask,
194 doc="CCD assembly task; used for defect box conversions.",
195 )
196
197 def validate(self):
198 super().validate()
199
201 raise NotImplementedError("Low signal non-linearity is not implemented.")
202
203 if self.doAddDeferredCharge and self.isTrimmed:
204 raise NotImplementedError("Must be untrimmed for mock serial CTI to"
205 "realistically add charge into overscan regions.")
206
207 def setDefaults(self):
208 super().setDefaults()
209
210 self.gaingain = 1.7 # Default value.
211 self.skyLevelskyLevel = 1700.0 # electron
212 self.sourceFluxsourceFlux = [50_000.0] # electron
213 self.sourceXsourceX = [35.0] # pixel
214 self.sourceYsourceY = [37.0] # pixel
215 self.overscanScaleoverscanScale = 170.0 # electron
216 self.biasLevelbiasLevel = 20_000.0 # adu
218
219
221 """Class to generate consistent mock images for ISR testing.
222 """
223 ConfigClass = IsrMockLSSTConfig
224 _DefaultName = "isrMockLSST"
225
226 def __init__(self, **kwargs):
227 super().__init__(**kwargs)
228
229 # Get kernel derived from imSim generated flats with BFE. The kernel
230 # was used for Ops Rehearsal 3 for LSSTCam-type sensors
231 # See https://rubinobs.atlassian.net/browse/DM-43059 for more details.
232 self.bfKernelbfKernel = np.array([[4.83499829e-01, 8.10171823e-01, 5.31096720e-01,
233 3.54369868e-02, -8.44782871e-01, -1.64614462e+00,
234 -3.83933101e+00, -5.60243416e+00, -6.51691578e+00,
235 -5.60243416e+00, -3.83933101e+00, -1.64614462e+00,
236 -8.44782871e-01, 3.54369868e-02, 5.31096720e-01,
237 8.10171823e-01, 4.83499829e-01],
238 [1.12382749e+00, 2.22609074e+00, 1.27877807e+00,
239 4.55434098e-01, -1.76842385e+00, -1.90046460e+00,
240 -8.10874526e+00, -1.20534899e+01, -1.48627948e+01,
241 -1.20534899e+01, -8.10874526e+00, -1.90046460e+00,
242 -1.76842385e+00, 4.55434098e-01, 1.27877807e+00,
243 2.22609074e+00, 1.12382749e+00],
244 [1.78571940e+00, 4.38918110e+00, 3.95098587e+00,
245 3.70961649e-01, -3.48151981e+00, -9.61567736e+00,
246 -1.78621172e+01, -2.32278872e+01, -2.31833727e+01,
247 -2.32278872e+01, -1.78621172e+01, -9.61567736e+00,
248 -3.48151981e+00, 3.70961649e-01, 3.95098587e+00,
249 4.38918110e+00, 1.78571940e+00],
250 [1.62986900e+00, 3.67851228e+00, 5.68645252e+00,
251 2.15342566e-01, -8.89937202e+00, -1.44739813e+01,
252 -2.98952660e+01, -4.37420817e+01, -4.83160958e+01,
253 -4.37420817e+01, -2.98952660e+01, -1.44739813e+01,
254 -8.89937202e+00, 2.15342566e-01, 5.68645252e+00,
255 3.67851228e+00, 1.62986900e+00],
256 [1.05524430e+00, 1.71917897e+00, 1.73105590e+00,
257 -2.10088420e+00, -1.15118208e+01, -2.55007598e+01,
258 -4.73056159e+01, -6.97257685e+01, -8.09264433e+01,
259 -6.97257685e+01, -4.73056159e+01, -2.55007598e+01,
260 -1.15118208e+01, -2.10088420e+00, 1.73105590e+00,
261 1.71917897e+00, 1.05524430e+00],
262 [8.71929228e-01, 5.41025574e-01, 9.47560771e-01,
263 -5.75314708e-01, -7.46104027e+00, -4.42314481e+01,
264 -9.54126971e+01, -1.61603201e+02, -2.07520692e+02,
265 -1.61603201e+02, -9.54126971e+01, -4.42314481e+01,
266 -7.46104027e+00, -5.75314708e-01, 9.47560771e-01,
267 5.41025574e-01, 8.71929228e-01],
268 [1.89144704e+00, 3.57543979e+00, -6.91419168e-02,
269 -3.37950835e+00, -1.46695089e+01, -7.22850746e+01,
270 -1.65563055e+02, -3.10820425e+02, -4.70026655e+02,
271 -3.10820425e+02, -1.65563055e+02, -7.22850746e+01,
272 -1.46695089e+01, -3.37950835e+00, -6.91419168e-02,
273 3.57543979e+00, 1.89144704e+00],
274 [3.11841913e+00, 7.84024994e+00, 1.88495248e+00,
275 -7.69011009e+00, -2.71782400e+01, -1.04343326e+02,
276 -2.47561370e+02, -5.32959841e+02, -1.16529012e+03,
277 -5.32959841e+02, -2.47561370e+02, -1.04343326e+02,
278 -2.71782400e+01, -7.69011009e+00, 1.88495248e+00,
279 7.84024994e+00, 3.11841913e+00],
280 [2.74197956e+00, 4.73107997e+00, -9.48352966e-01,
281 -9.44822832e+00, -3.06477671e+01, -1.26788739e+02,
282 -3.22828411e+02, -8.47943472e+02, -3.87702420e+03,
283 -8.47943472e+02, -3.22828411e+02, -1.26788739e+02,
284 -3.06477671e+01, -9.44822832e+00, -9.48352966e-01,
285 4.73107997e+00, 2.74197956e+00],
286 [3.11841913e+00, 7.84024994e+00, 1.88495248e+00,
287 -7.69011009e+00, -2.71782400e+01, -1.04343326e+02,
288 -2.47561370e+02, -5.32959841e+02, -1.16529012e+03,
289 -5.32959841e+02, -2.47561370e+02, -1.04343326e+02,
290 -2.71782400e+01, -7.69011009e+00, 1.88495248e+00,
291 7.84024994e+00, 3.11841913e+00],
292 [1.89144704e+00, 3.57543979e+00, -6.91419168e-02,
293 -3.37950835e+00, -1.46695089e+01, -7.22850746e+01,
294 -1.65563055e+02, -3.10820425e+02, -4.70026655e+02,
295 -3.10820425e+02, -1.65563055e+02, -7.22850746e+01,
296 -1.46695089e+01, -3.37950835e+00, -6.91419168e-02,
297 3.57543979e+00, 1.89144704e+00],
298 [8.71929228e-01, 5.41025574e-01, 9.47560771e-01,
299 -5.75314708e-01, -7.46104027e+00, -4.42314481e+01,
300 -9.54126971e+01, -1.61603201e+02, -2.07520692e+02,
301 -1.61603201e+02, -9.54126971e+01, -4.42314481e+01,
302 -7.46104027e+00, -5.75314708e-01, 9.47560771e-01,
303 5.41025574e-01, 8.71929228e-01],
304 [1.05524430e+00, 1.71917897e+00, 1.73105590e+00,
305 -2.10088420e+00, -1.15118208e+01, -2.55007598e+01,
306 -4.73056159e+01, -6.97257685e+01, -8.09264433e+01,
307 -6.97257685e+01, -4.73056159e+01, -2.55007598e+01,
308 -1.15118208e+01, -2.10088420e+00, 1.73105590e+00,
309 1.71917897e+00, 1.05524430e+00],
310 [1.62986900e+00, 3.67851228e+00, 5.68645252e+00,
311 2.15342566e-01, -8.89937202e+00, -1.44739813e+01,
312 -2.98952660e+01, -4.37420817e+01, -4.83160958e+01,
313 -4.37420817e+01, -2.98952660e+01, -1.44739813e+01,
314 -8.89937202e+00, 2.15342566e-01, 5.68645252e+00,
315 3.67851228e+00, 1.62986900e+00],
316 [1.78571940e+00, 4.38918110e+00, 3.95098587e+00,
317 3.70961649e-01, -3.48151981e+00, -9.61567736e+00,
318 -1.78621172e+01, -2.32278872e+01, -2.31833727e+01,
319 -2.32278872e+01, -1.78621172e+01, -9.61567736e+00,
320 -3.48151981e+00, 3.70961649e-01, 3.95098587e+00,
321 4.38918110e+00, 1.78571940e+00],
322 [1.12382749e+00, 2.22609074e+00, 1.27877807e+00,
323 4.55434098e-01, -1.76842385e+00, -1.90046460e+00,
324 -8.10874526e+00, -1.20534899e+01, -1.48627948e+01,
325 -1.20534899e+01, -8.10874526e+00, -1.90046460e+00,
326 -1.76842385e+00, 4.55434098e-01, 1.27877807e+00,
327 2.22609074e+00, 1.12382749e+00],
328 [4.83499829e-01, 8.10171823e-01, 5.31096720e-01,
329 3.54369868e-02, -8.44782871e-01, -1.64614462+00,
330 -3.83933101e+00, -5.60243416e+00, -6.51691578e+00,
331 -5.60243416e+00, -3.83933101e+00, -1.64614462e+00,
332 -8.44782871e-01, 3.54369868e-02, 5.31096720e-01,
333 8.10171823e-01, 4.83499829e-01]]) * 1e-10
334
335 # Spline trap coefficients and the ctiCalibDict are all taken from a
336 # cti calibration measured from LSSTCam sensor R03_S12 during Run 5
337 # EO testing. These are the coefficients for the spline trap model
338 # used in the deferred charge calibration. The collection can be
339 # found in /repo/ir2: u/abrought/13144/cti (processed 3/4/2024).
340 self.splineTrapCoeffs = np.array([0.0, 28.1, 47.4, 56.4, 66.6, 78.6, 92.4, 109.4,
341 129.0, 151.9, 179.4, 211.9, 250.5, 296.2, 350.0,
342 413.5, 488.0, 576.0, 680.4, 753.0, 888.2, 1040.5,
343 1254.1, 1478.9, 1747.0, 2055.7, 2416.9, 2855.2,
344 3361.9, 3969.4, 4665.9, 5405.3, 6380.0, 7516.7,
345 8875.9, 10488.6, 12681.9, 14974.2, 17257.6, 20366.5,
346 24026.7, 28372.1, 33451.7, 39550.4, 46624.8, 55042.9,
347 64862.7, 76503.1, 90265.6, 106384.2, 0.0, 0.0, 0.0,
348 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1,
349 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.0,
350 0.1, 0.2, 0.6, 0.1, 0.0, 0.1, 0.0, 0.6, 0.3, 0.5, 0.8,
351 0.8, 1.5, 2.0, 1.8, 2.4, 2.6, 3.7, 5.0, 6.4, 8.4, 10.9,
352 14.5, 21.1, 28.9])
353
354 self.ctiCalibDict = {'driftScale': {'C:0,0': 0.000127,
355 'C:0,1': 0.000137,
356 'C:0,2': 0.000138,
357 'C:0,3': 0.000147,
358 'C:1,0': 0.000147,
359 'C:1,1': 0.000122,
360 'C:1,2': 0.000123,
361 'C:1,3': 0.000116},
362 'decayTime': {'C:0,0': 2.30,
363 'C:0,1': 2.21,
364 'C:0,2': 2.28,
365 'C:0,3': 2.34,
366 'C:1,0': 2.30,
367 'C:1,1': 2.40,
368 'C:1,2': 2.51,
369 'C:1,3': 2.21},
370 'globalCti': {'C:0,0': 5.25e-07,
371 'C:0,1': 5.38e-07,
372 'C:0,2': 5.80e-07,
373 'C:0,3': 5.91e-07,
374 'C:1,0': 6.24e-07,
375 'C:1,1': 5.72e-07,
376 'C:1,2': 5.60e-07,
377 'C:1,3': 4.40e-07},
378 'serialTraps': {'C:0,0': {'size': 20000.0,
379 'emissionTime': 0.4,
380 'pixel': 1,
381 'trap_type': 'spline',
382 'coeffs': self.splineTrapCoeffs},
383 'C:0,1': {'size': 20000.0,
384 'emissionTime': 0.4,
385 'pixel': 1,
386 'trap_type': 'spline',
387 'coeffs': self.splineTrapCoeffs},
388 'C:0,2': {'size': 20000.0,
389 'emissionTime': 0.4,
390 'pixel': 1,
391 'trap_type': 'spline',
392 'coeffs': self.splineTrapCoeffs},
393 'C:0,3': {'size': 20000.0,
394 'emissionTime': 0.4,
395 'pixel': 1,
396 'trap_type': 'spline',
397 'coeffs': self.splineTrapCoeffs},
398 'C:1,0': {'size': 20000.0,
399 'emissionTime': 0.4,
400 'pixel': 1,
401 'trap_type': 'spline',
402 'coeffs': self.splineTrapCoeffs},
403 'C:1,1': {'size': 20000.0,
404 'emissionTime': 0.4,
405 'pixel': 1,
406 'trap_type': 'spline',
407 'coeffs': self.splineTrapCoeffs},
408 'C:1,2': {'size': 20000.0,
409 'emissionTime': 0.4,
410 'pixel': 1,
411 'trap_type': 'spline',
412 'coeffs': self.splineTrapCoeffs},
413 'C:1,3': {'size': 20000.0,
414 'emissionTime': 0.4,
415 'pixel': 1,
416 'trap_type': 'spline',
417 'coeffs': self.splineTrapCoeffs}}}
418
420
421 # Cross-talk coeffs are defined in the parent class.
422
423 self.makeSubtask("assembleCcd")
424
425 def run(self):
426 """Generate a mock ISR product following LSSTCam ISR, and return it.
427
428 Returns
429 -------
430 image : `lsst.afw.image.Exposure`
431 Simulated ISR image with signals added.
432 dataProduct :
433 Simulated ISR data products.
434 None :
435 Returned if no valid configuration was found.
436
437 Raises
438 ------
439 RuntimeError
440 Raised if both doGenerateImage and doGenerateData are specified.
441 """
442 if self.config.doGenerateImage and self.config.doGenerateData:
443 raise RuntimeError("Only one of doGenerateImage and doGenerateData may be specified.")
444 elif self.config.doGenerateImage:
445 return self.makeImagemakeImage()
446 elif self.config.doGenerateData:
447 return self.makeData()
448 else:
449 return None
450
451 def makeImage(self):
452 """Generate a simulated ISR LSST image.
453
454 Returns
455 -------
456 exposure : `lsst.afw.image.Exposure` or `dict`
457 Simulated ISR image data.
458
459 Notes
460 -----
461 This method constructs a "raw" data image.
462 """
463 exposure = self.getExposure()
464
465 # Set up random number generators for consistency of components,
466 # no matter the group that are configured.
467 rngSky = np.random.RandomState(seed=self.config.rngSeed + 1)
468 rngDark = np.random.RandomState(seed=self.config.rngSeed + 2)
469 rng2DBias = np.random.RandomState(seed=self.config.rngSeed + 3)
470 rngOverscan = np.random.RandomState(seed=self.config.rngSeed + 4)
471 rngReadNoise = np.random.RandomState(seed=self.config.rngSeed + 5)
472 rngBrighterFatter = galsim.BaseDeviate(self.config.rngSeed + 6)
473
474 # Create the linearizer if we will need it.
475 if self.config.doAddHighSignalNonlinearity:
476 linearizer = LinearizerMockLSST().run()
477
478 # We introduce effects as they happen from a source to the signal,
479 # so the effects go from electron to adu.
480 # The ISR steps will then correct these effects in the reverse order.
481 for idx, amp in enumerate(exposure.getDetector()):
482 # Get image bbox and data
483 bbox = None
484 if self.config.isTrimmed:
485 bbox = amp.getBBox()
486 bboxFull = bbox
487 else:
488 bbox = amp.getRawDataBBox()
489 bboxFull = amp.getRawBBox()
490
491 # This is the image data (excluding pre/overscans).
492 ampImageData = exposure.image[bbox]
493 # This is the full data (including pre/overscans if untrimmed).
494 ampFullData = exposure.image[bboxFull]
495
496 # Astrophysical signals are all in electron (e-).
497 # These are only applied to the imaging portion of the
498 # amplifier (ampImageData)
499
500 if self.config.doAddSky:
501 # The sky effects are in electron.
503 ampImageData,
504 self.config.skyLevel,
505 np.sqrt(self.config.skyLevel),
506 rng=rngSky,
507 )
508
509 if self.config.doAddSource:
510 for sourceAmp, sourceFlux, sourceX, sourceY in zip(self.config.sourceAmp,
511 self.config.sourceFlux,
512 self.config.sourceX,
513 self.config.sourceY):
514 if idx == sourceAmp:
515 # The source flux is in electron.
516 self.amplifierAddSource(ampImageData, sourceFlux, sourceX, sourceY)
517
518 if self.config.doAddFringe:
519 # Fringes are added in electron.
520 self.amplifierAddFringe(amp,
521 ampImageData,
522 np.array(self.config.fringeScale),
523 x0=np.array(self.config.fringeX0),
524 y0=np.array(self.config.fringeY0))
525
526 if self.config.doAddFlat:
527 if self.config.calibMode:
528 # In case we are making a combined flat,
529 # add a non-zero signal so the mock flat can be multiplied
530 self.amplifierAddNoise(ampImageData, 1.0, 0.0)
531 # Multiply each amplifier by a Gaussian centered on u0 and v0
532 u0 = exposure.getDetector().getBBox().getDimensions().getX()/2.
533 v0 = exposure.getDetector().getBBox().getDimensions().getY()/2.
534 self.amplifierMultiplyFlatamplifierMultiplyFlat(amp, ampImageData, self.config.flatDrop, u0=u0, v0=v0)
535
536 # On-chip electronic effects.
537
538 # 1. Add bright defect(s).
539 if self.config.doAddBrightDefects:
540 defectList = self.makeDefectListmakeDefectList(isTrimmed=self.config.isTrimmed)
541
542 for defect in defectList:
543 exposure.image[defect.getBBox()] = self.config.brightDefectLevel
544
545 for idx, amp in enumerate(exposure.getDetector()):
546 # Get image bbox and data
547 bbox = None
548 if self.config.isTrimmed:
549 bbox = amp.getBBox()
550 bboxFull = bbox
551 else:
552 bbox = amp.getRawDataBBox()
553 bboxFull = amp.getRawBBox()
554
555 # This is the image data (excluding pre/overscans).
556 ampImageData = exposure.image[bbox]
557 # This is the full data (including pre/overscans if untrimmed).
558 ampFullData = exposure.image[bboxFull]
559
560 # 2. Add dark current (electron) to imaging portion of the amp.
561 if self.config.doAddDark or self.config.doAddDarkNoiseOnly:
562 if self.config.doAddDarkNoiseOnly:
563 darkLevel = 0.0
564 else:
565 darkLevel = self.config.darkRate * self.config.darkTime
566 if self.config.calibMode:
567 darkNoise = 0.0
568 else:
569 darkNoise = np.sqrt(self.config.darkRate * self.config.darkTime)
570
571 self.amplifierAddNoise(ampImageData, darkLevel, darkNoise, rng=rngDark)
572
573 # 3. Add BF effect (electron) to imaging portion of the amp.
574 if self.config.doAddBrighterFatter is True:
576 ampImageData,
577 rngBrighterFatter,
578 self.config.bfStrength,
579 self.config.nRecalc,
580 )
581
582 # 4. Add serial CTI (electron) to amplifier (imaging + overscan).
583 if self.config.doAddDeferredCharge:
584 # Get the free charge area for the amplifier.
585 self.amplifierAddDeferredCharge(exposure, amp)
586
587 # 5. Add 2D bias residual (electron) to imaging portion of the amp.
588 if self.config.doAdd2DBias:
589 # For now we use an unstructured noise field to add some
590 # consistent 2D bias residual that can be subtracted. In
591 # the future this can be made into a warm corner (for example).
593 ampImageData,
594 0.0,
595 self.config.noise2DBias,
596 rng=rng2DBias,
597 )
598
599 # 6. Add clock-injected offset (electron) to amplifer
600 # (imaging + overscan).
601 # This is just an offset that will be crosstalked and modified by
602 # the gain, and does not have a noise associated with it.
603 if self.config.doAddClockInjectedOffset:
605 ampFullData,
606 self.config.clockInjectedOffsetLevel,
607 0.0,
608 )
609
610 # 7./8. Add serial and parallel overscan slopes (electron)
611 # (imaging + overscan)
612 if (self.config.doAddParallelOverscanRamp or self.config.doAddSerialOverscanRamp) and \
613 not self.config.isTrimmed:
614
615 if self.config.doAddParallelOverscanRamp:
616 # Apply gradient along the X axis.
617 self.amplifierAddXGradient(ampFullData, -1.0 * self.config.overscanScale,
618 1.0 * self.config.overscanScale)
619
620 if self.config.doAddSerialOverscanRamp:
621 # Apply the gradient along the Y axis.
622 self.amplifierAddYGradient(ampFullData, -1.0 * self.config.overscanScale,
623 1.0 * self.config.overscanScale)
624
625 # 9. Add non-linearity (electron) to amplifier
626 # (imaging + overscan).
627 if self.config.doAddHighSignalNonlinearity:
628 # The linearizer coefficients come from makeLinearizer().
629 if linearizer.linearityType[amp.getName()] != "Spline":
630 raise RuntimeError("IsrMockLSST only supports spline non-linearity.")
631
632 coeffs = linearizer.linearityCoeffs[amp.getName()]
633 centers, values = np.split(coeffs, 2)
634
635 # This is an application of high signal non-linearity, so we
636 # set the lower values to 0.0 (this cut is arbitrary).
637 values[centers < self.config.highSignalNonlinearityThreshold] = 0.0
638
639 # The linearizer is units of adu, so convert to electron
640 values *= self.config.gainDict[amp.getName()]
641
642 # Note that the linearity spline is in "overscan subtracted"
643 # units so needs to be applied without the clock-injected
644 # offset.
646 ampFullData,
647 centers,
648 values,
649 self.config.clockInjectedOffsetLevel if self.config.doAddClockInjectedOffset else 0.0,
650 )
651
652 # 10. Add read noise (electron) to the amplifier
653 # (imaging + overscan).
654 # Unsure if this should be before or after crosstalk.
655 # Probably some of both; hopefully doesn't matter.
656 if not self.config.calibMode:
657 # Add read noise to the imaging region.
659 ampImageData,
660 0.0,
661 self.config.readNoise,
662 rng=rngReadNoise,
663 )
664
665 # If not trimmed, add to the overscan regions.
666 if not self.config.isTrimmed:
667 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
668 parallelOverscanData = exposure.image[parallelOverscanBBox]
669
670 serialOverscanBBox = self.getFullSerialOverscanBBox(amp)
671 serialOverscanData = exposure.image[serialOverscanBBox]
672
673 # Add read noise of mean 0
674 # to the parallel and serial overscan regions.
676 parallelOverscanData,
677 0.0,
678 self.config.readNoise,
679 rng=rngOverscan,
680 )
682 serialOverscanData,
683 0.0,
684 self.config.readNoise,
685 rng=rngOverscan,
686 )
687
688 # 7b. Add bad column to the parallel overscan region.
689 if self.config.doAddBadParallelOverscanColumn and not self.config.isTrimmed:
690 # We want to place this right above the defect, to simulate
691 # bleeding into the parallel overscan region.
692 amp = exposure.getDetector()[2]
693 parBBox = amp.getRawParallelOverscanBBox()
694 bboxBad = geom.Box2I(
695 corner=geom.Point2I(50, parBBox.getMinY()),
696 dimensions=geom.Extent2I(1, parBBox.getHeight()),
697 )
698 exposure[bboxBad].image.array[:, :] = self.config.badParallelOverscanColumnLevel
699
700 if self.config.doAddBadParallelOverscanColumnNeighbors:
701 for neighbor in [49, 51]:
702 bboxBad = geom.Box2I(
703 corner=geom.Point2I(neighbor, parBBox.getMinY()),
704 dimensions=geom.Extent2I(1, parBBox.getHeight()),
705 )
706 exposure[bboxBad].image.array[:, :] += self.config.badParallelOverscanColumnNeighborsLevel
707
708 # 11. Add crosstalk (electron) to all the amplifiers
709 # (imaging + overscan).
710 if self.config.doAddCrosstalk:
711 ctCalib = CrosstalkCalib()
712 exposureClean = exposure.clone()
713 for idxS, ampS in enumerate(exposure.getDetector()):
714 for idxT, ampT in enumerate(exposure.getDetector()):
715 ampDataTarget = exposure.image[ampT.getBBox() if self.config.isTrimmed
716 else ampT.getRawBBox()]
717 ampDataSource = ctCalib.extractAmp(exposureClean.image, ampS, ampT,
718 isTrimmed=self.config.isTrimmed,
719 fullAmplifier=True)
720 self.amplifierAddCT(ampDataSource, ampDataTarget, self.crosstalkCoeffs[idxS][idxT])
721
722 for amp in exposure.getDetector():
723 # Get image bbox and data (again).
724 bbox = None
725 if self.config.isTrimmed:
726 bbox = amp.getBBox()
727 bboxFull = bbox
728 else:
729 bbox = amp.getRawDataBBox()
730 bboxFull = amp.getRawBBox()
731
732 # This is the image data (excluding pre/overscans).
733 ampImageData = exposure.image[bbox]
734 # This is the full data (including pre/overscans if untrimmed).
735 ampFullData = exposure.image[bboxFull]
736
737 # 12. Gain un-normalize (from electron to floating point adu)
738 if self.config.doApplyGain:
739 gain = self.config.gainDict.get(amp.getName(), self.config.gain)
740 self.applyGain(ampFullData, gain)
741
742 # 13. Add overall bias level (adu) to the amplifier
743 # (imaging + overscan)
744 if self.config.doAddBias:
745 self.addBiasLevel(ampFullData, self.config.biasLevel)
746
747 # 14. Round/Truncate to integers (adu)
748 if self.config.doRoundAdu:
749 self.roundADU(ampFullData)
750
751 # Add units metadata to calibrations.
752 if self.config.calibMode:
753 if self.config.doApplyGain:
754 exposure.metadata["LSST ISR UNITS"] = "adu"
755 else:
756 exposure.metadata["LSST ISR UNITS"] = "electron"
757
758 if self.config.doGenerateAmpDict:
759 expDict = dict()
760 for amp in exposure.getDetector():
761 expDict[amp.getName()] = exposure
762 return expDict
763 else:
764 return exposure
765
766 def addBiasLevel(self, ampData, biasLevel):
767 """Add bias level to an amplifier's image data.
768
769 Parameters
770 ----------
771 ampData : `lsst.afw.image.ImageF`
772 Amplifier image to operate on.
773 biasLevel : `float`
774 Bias level to be added to the image.
775 """
776 ampArr = ampData.array
777 ampArr[:] = ampArr[:] + biasLevel
778
779 def makeDefectList(self, isTrimmed=True):
780 """Generate a simple defect list.
781
782 Parameters
783 ----------
784 isTrimmed : `bool`, optional
785 Return defects in trimmed coordinates?
786
787 Returns
788 -------
789 defectList : `lsst.meas.algorithms.Defects`
790 Simulated defect list
791 """
792 defectBoxesUntrimmed = [
794 geom.Point2I(50, 118),
795 geom.Extent2I(1, 51),
796 ),
797 ]
798
799 if not isTrimmed:
800 return Defects(defectBoxesUntrimmed)
801
802 # If trimmed, we need to convert.
803 tempExp = self.getExposure(isTrimmed=False)
804 tempExp.image.array[:, :] = 0.0
805 for bbox in defectBoxesUntrimmed:
806 tempExp.image[bbox] = 1.0
807
808 assembledExp = self.assembleCcd.assembleCcd(tempExp)
809
810 # Use thresholding code to find defect footprints/boxes.
811 threshold = afwDetection.createThreshold(1.0, "value", polarity=True)
812 footprintSet = afwDetection.FootprintSet(assembledExp.image, threshold)
813
814 return Defects.fromFootprintList(footprintSet.getFootprints())
815
816 def makeBfKernel(self):
817 """Generate a simple simulated brighter-fatter kernel.
818 Returns
819 -------
820 kernel : `lsst.ip.isr.BrighterFatterKernel`
821 Simulated brighter-fatter kernel.
822 """
823 bfkArray = super().makeBfKernel()
824 bfKernelObject = BrighterFatterKernel()
825 bfKernelObject.level = 'AMP'
826 bfKernelObject.gain = self.config.gainDict
827
828 for amp in self.getExposure().getDetector():
829 # Kernel must be in (y,x) orientation
830 bfKernelObject.ampKernels[amp.getName()] = bfkArray.T
831
832 return bfKernelObject
833
835 """Generate a CTI calibration.
836
837 Returns
838 -------
839 cti : `lsst.ip.isr.deferredCharge.DeferredChargeCalib`
840 Simulated deferred charge calibration.
841 """
842
843 metadataDict = {'metadata': PropertyList()}
844 metadataDict['metadata'].add(name="OBSTYPE", value="CTI")
845 metadataDict['metadata'].add(name="CALIBCLS",
846 value="lsst.ip.isr.deferredCharge.DeferredChargeCalib")
847 self.ctiCalibDict = {**metadataDict, **self.ctiCalibDict}
848 deferredChargeCalib = DeferredChargeCalib()
849 self.cti = deferredChargeCalib.fromDict(self.ctiCalibDict)
850
851 return self.cti
852
853 def amplifierAddBrighterFatter(self, ampImageData, rng, bfStrength, nRecalc):
854 """Add brighter fatter effect and/or diffusion to the image.
855 Parameters
856 ----------
857 ampImageData : `lsst.afw.image.ImageF`
858 Amplifier image to operate on.
859 rng : `galsim.BaseDeviate`
860 Random number generator.
861 bfStrength : `float`
862 Scaling parameter of the brighter fatter effect (nominally = 1)
863 nRecalc: 'int'
864 The number of electrons to accumulate before recalculating the
865 distortion of the pixel shapes.
866 """
867
868 incidentImage = galsim.Image(ampImageData.array, scale=1)
869 measuredImage = galsim.ImageF(
870 ampImageData.array.shape[1],
871 ampImageData.array.shape[0],
872 scale=1,
873 )
874 photons = galsim.PhotonArray.makeFromImage(incidentImage)
875
876 sensorModel = galsim.SiliconSensor(
877 strength=bfStrength,
878 rng=rng,
879 diffusion_factor=0.0,
880 nrecalc=nRecalc,
881 )
882
883 totalFluxAdded = sensorModel.accumulate(photons, measuredImage)
884 ampImageData.array = measuredImage.array
885
886 return totalFluxAdded
887
888 def amplifierAddDeferredCharge(self, exposure, amp):
889 """Add serial CTI to the amplifier data.
890
891 Parameters
892 ----------
893 exposure : `lsst.afw.image.ExposureF`
894 The exposure object containing the amplifier
895 to apply deferred charge to.
896 amp : `lsst.afw.image.Amplifier`
897 The amplifier object (contains geometry info).
898 """
899 # Get the amplifier's geometry parameters.
900 # When adding deferred charge, we have already assured that
901 # isTrimmed is False. Therefore we want to make sure that we
902 # get the RawDataBBox.
903 readoutCorner = amp.getReadoutCorner()
904 prescanWidth = amp.getRawHorizontalPrescanBBox().getWidth()
905 serialOverscanWidth = amp.getRawHorizontalOverscanBBox().getWidth()
906 parallelOverscanWidth = amp.getRawVerticalOverscanBBox().getHeight()
907 bboxFreeCharge = amp.getRawDataBBox()
908 bboxFreeCharge = bboxFreeCharge.expandedTo(amp.getRawHorizontalOverscanBBox())
909 bboxFreeCharge = bboxFreeCharge.expandedTo(amp.getRawHorizontalPrescanBBox())
910 bboxFreeCharge = bboxFreeCharge.expandedTo(amp.getRawVerticalOverscanBBox())
911
912 ampFreeChargeData = exposure.image[bboxFreeCharge]
913 ampImageData = exposure.image[amp.getRawDataBBox()]
914
915 # Get the deferred charge parameters for this amplifier.
916 cti = self.deferredChargeCalib.globalCti[amp.getName()]
917 traps = self.deferredChargeCalib.serialTraps[amp.getName()]
918 driftScale = self.deferredChargeCalib.driftScale[amp.getName()]
919 decayTime = self.deferredChargeCalib.decayTime[amp.getName()]
920
921 # Create a fake amplifier object that contains some deferred charge
922 # paramters.
923 floatingOutputAmplifier = FloatingOutputAmplifier(
924 gain=1.0, # Everything is already in electrons.
925 scale=driftScale,
926 decay_time=decayTime,
927 noise=0.0,
928 offset=0.0,
929 )
930
931 def flipImage(arr, readoutCorner):
932 # Flip an array so that the readout corner is in
933 # the lower left.
934 if readoutCorner == ReadoutCorner.LR:
935 return np.fliplr(arr)
936 elif readoutCorner == ReadoutCorner.UR:
937 return np.fliplr(np.flipud(arr))
938 elif readoutCorner == ReadoutCorner.UL:
939 return np.flipud(arr)
940 else:
941 pass
942
943 return arr
944
945 # The algorithm expects that the readout corner is in
946 # the lower left corner. Flip it to be so:
947 imageData = ampImageData.array
948 imageData = flipImage(imageData, readoutCorner)
949
950 # Simulate the amplifier.
951 ampSim = SegmentSimulator(
952 imarr=imageData,
953 prescan_width=prescanWidth,
954 output_amplifier=floatingOutputAmplifier,
955 cti=cti,
956 traps=traps,
957 )
958
959 # Simulate deferred charge!
960 # Note that the readout() method uses the image region data and the
961 # overscan dimensions provided as input parameters. It then creates
962 # overscan nd adds it to the image data to create the raw image.
963 result = ampSim.readout(
964 serial_overscan_width=serialOverscanWidth,
965 parallel_overscan_width=parallelOverscanWidth,
966 )
967
968 # Flip the image back to the original orientation.
969 result = flipImage(result, readoutCorner)
970
971 # Set the image with the deferred charge added.
972 ampFreeChargeData.array[:, :] = result
973
974 def makeLinearizer(self):
975 # docstring inherited.
976
977 # The linearizer has units of adu.
978 nNodes = 10
979 # Set this to just above the mock saturation (adu)
980 maxADU = 101_000
981 nonLinSplineNodes = np.linspace(0, maxADU, nNodes)
982 # These values come from cp_pipe/tests/test_linearity.py and
983 # are based on a test fit to LSSTCam data, run 7193D, detector 22,
984 # amp C00.
985 nonLinSplineValues = np.array(
986 [0.0, -8.87, 1.46, 1.69, -6.92, -68.23, -78.01, -11.56, 80.26, 185.01]
987 )
988
989 if self.config.doAddHighSignalNonlinearity and not self.config.doAddLowSignalNonlinearity:
990 nonLinSplineValues[nonLinSplineNodes < self.config.highSignalNonlinearityThreshold] = 0.0
991 elif self.config.doAddLowSignalNonlinearity:
992 raise NotImplementedError("Low signal non-linearity is not implemented.")
993
994 exp = self.getExposure()
995 detector = exp.getDetector()
996
997 linearizer = Linearizer(detector=detector)
998 linearizer.updateMetadataFromExposures([exp])
999
1000 # We need to set override by hand because we are constructing a
1001 # linearizer manually and not from a serialized object.
1002 linearizer.override = True
1003 linearizer.hasLinearity = True
1004 linearizer.validate()
1005 linearizer.updateMetadata(camera=self.getCamera(), detector=detector, filterName='NONE')
1006 linearizer.updateMetadata(setDate=True, setCalibId=True)
1007
1008 for amp in detector:
1009 ampName = amp.getName()
1010 linearizer.linearityType[ampName] = "Spline"
1011 linearizer.linearityCoeffs[ampName] = np.concatenate([nonLinSplineNodes, nonLinSplineValues])
1012 # We need to specify the raw bbox here.
1013 linearizer.linearityBBox[ampName] = amp.getRawBBox()
1014
1015 return linearizer
1016
1017 def amplifierAddNonlinearity(self, ampData, centers, values, offset):
1018 """Add non-linearity to amplifier data.
1019
1020 Parameters
1021 ----------
1022 ampData : `lsst.afw.image.ImageF`
1023 Amplifier image to operate on.
1024 centers : `np.ndarray`
1025 Spline nodes.
1026 values : `np.ndarray`
1027 Spline values.
1028 offset : `float`
1029 Offset zero-point between linearizer (internal vs external).
1030 """
1031 # I'm not sure what to do about negative values...
1032
1033 # Note that we are using the afw AKIMA_SPLINE to offset the
1034 # data but using the equivalent but faster scipy Akima1DInterpolator to
1035 # correct the data.
1037 centers,
1038 values,
1039 afwMath.stringToInterpStyle("AKIMA_SPLINE"),
1040 )
1041
1042 delta = np.asarray(spl.interpolate(ampData.array.ravel() - offset))
1043
1044 ampData.array[:, :] += delta.reshape(ampData.array.shape)
1045
1046 def amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0):
1047 """Multiply an amplifier's image data by a flat-like pattern.
1048
1049 Parameters
1050 ----------
1051 amp : `lsst.afw.ampInfo.AmpInfoRecord`
1052 Amplifier to operate on. Needed for amp<->exp coordinate
1053 transforms.
1054 ampData : `lsst.afw.image.ImageF`
1055 Amplifier image to operate on.
1056 fracDrop : `float`
1057 Fractional drop from center to edge of detector along x-axis.
1058 u0 : `float`
1059 Peak location in detector coordinates.
1060 v0 : `float`
1061 Peak location in detector coordinates.
1062 """
1063 if fracDrop >= 1.0:
1064 raise RuntimeError("Flat fractional drop cannot be greater than 1.0")
1065
1066 sigma = u0 / np.sqrt(2.0 * fracDrop)
1067
1068 for x in range(0, ampData.getDimensions().getX()):
1069 for y in range(0, ampData.getDimensions().getY()):
1070 (u, v) = self.localCoordToExpCoord(amp, x, y)
1071 f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
1072 ampData.array[y][x] = (ampData.array[y][x] * f)
1073
1074 def applyGain(self, ampData, gain):
1075 """Apply gain to the amplifier's data.
1076 This method divides the data by the gain
1077 because the mocks need to convert the data in electron to adu,
1078 so it does the inverse operation to applyGains in isrFunctions.
1079
1080 Parameters
1081 ----------
1082 ampData : `lsst.afw.image.ImageF`
1083 Amplifier image to operate on.
1084 gain : `float`
1085 Gain value in electron/adu.
1086 """
1087 ampArr = ampData.array
1088 ampArr[:] = ampArr[:] / gain
1089
1090 def roundADU(self, ampData):
1091 """Round adu to nearest integer.
1092
1093 Parameters
1094 ----------
1095 ampData : `lsst.afw.image.ImageF`
1096 Amplifier image to operate on.
1097 """
1098 ampArr = ampData.array
1099 ampArr[:] = np.around(ampArr)
1100
1101 def amplifierAddXGradient(self, ampData, start, end):
1102 """Add a x-axis linear gradient to an amplifier's image data.
1103
1104 This method operates in the amplifier coordinate frame.
1105
1106 Parameters
1107 ----------
1108 ampData : `lsst.afw.image.ImageF`
1109 Amplifier image to operate on.
1110 start : `float`
1111 Start value of the gradient (at x=0).
1112 end : `float`
1113 End value of the gradient (at x=xmax).
1114 """
1115 nPixX = ampData.getDimensions().getX()
1116 ampArr = ampData.array
1117 ampArr[:] = ampArr[:] + (np.interp(range(nPixX), (0, nPixX - 1), (start, end)).reshape(1, nPixX)
1118 + np.zeros(ampData.getDimensions()).transpose())
1119
1121 """Get the full serial overscan bounding box from an amplifier.
1122
1123 This includes the serial/parallel overscan region.
1124
1125 Parameters
1126 ----------
1127 amp : `lsst.afw.ampInfo.AmpInfoRecord`
1128 Amplifier to operate on.
1129
1130 Returns
1131 -------
1132 bbox : `lsst.geom.Box2I`
1133 """
1134 # This only works for untrimmed data.
1135 bbox = amp.getRawDataBBox()
1136
1137 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1138 grownImageBBox = bbox.expandedTo(parallelOverscanBBox)
1139
1140 serialOverscanBBox = amp.getRawSerialOverscanBBox()
1141 # Extend the serial overscan bbox to include corners
1142 serialOverscanBBox = geom.Box2I(
1143 geom.Point2I(serialOverscanBBox.getMinX(),
1144 grownImageBBox.getMinY()),
1145 geom.Extent2I(serialOverscanBBox.getWidth(),
1146 grownImageBBox.getHeight()))
1147
1148 return serialOverscanBBox
1149
1150
1152 """Generate a raw exposure suitable for ISR.
1153 """
1154 def __init__(self, **kwargs):
1155 super().__init__(**kwargs)
1156 self.config.isTrimmed = False
1157 self.config.doGenerateImage = True
1158 self.config.doGenerateAmpDict = False
1159
1160 # Add astro effects
1161 self.config.doAddSky = True
1162 self.config.doAddSource = True
1163
1164 # Add optical effects
1165 self.config.doAddFringe = True
1166
1167 # Add instrument effects
1168 self.config.doAddParallelOverscanRamp = True
1169 self.config.doAddSerialOverscanRamp = True
1170 self.config.doAddCrosstalk = True
1171 self.config.doAddBias = True
1172 self.config.doAddDark = True
1173
1174 self.config.doAddFlat = True
1175
1176
1178 """Generate a trimmed raw exposure.
1179 """
1180 def __init__(self, **kwargs):
1181 super().__init__(**kwargs)
1182 self.config.isTrimmed = True
1183 self.config.doAddParallelOverscanRamp = False
1184 self.config.doAddSerialOverscanRamp = False
1185
1186
1188 """Generate a trimmed raw exposure.
1189
1190 This represents a "truth" image that can be compared to a
1191 post-ISR cleaned image.
1192 """
1193 def __init__(self, **kwargs):
1194 super().__init__(**kwargs)
1195 self.config.isTrimmed = True
1196 self.config.doGenerateImage = True
1197
1198 self.config.doAddSky = True
1199 self.config.doAddSource = True
1200
1201 self.config.doAddFringe = True
1202
1203 self.config.doAddParallelOverscanRamp = False
1204 self.config.doAddSerialOverscanRamp = False
1205 self.config.doAddCrosstalk = False
1206 self.config.doAddBias = False
1207 self.config.doAdd2DBias = False
1208 self.config.doAddDark = False
1209 self.config.doApplyGain = False
1210 self.config.doAddFlat = False
1211 self.config.doAddClockInjectedOffset = False
1212
1213 self.config.biasLevel = 0.0
1214 # Assume combined calibrations are made with 16 inputs.
1215 self.config.readNoise *= 0.25
1216
1217 self.config.doRoundAdu = False
1218
1219
1221 """Parent class for those that make reference calibrations.
1222 """
1223 def __init__(self, **kwargs):
1224 # If we want the calibration in adu units, we need to apply
1225 # the gain. Default is electron units, so do not apply the gain.
1226 doApplyGain = kwargs.pop("adu", False)
1227
1228 super().__init__(**kwargs)
1229 self.config.isTrimmed = True
1230 self.config.doGenerateImage = True
1231
1232 self.config.calibMode = True
1233
1234 self.config.doAddSky = False
1235 self.config.doAddSource = False
1236
1237 self.config.doAddFringe = False
1238
1239 self.config.doAddParallelOverscanRamp = False
1240 self.config.doAddSerialOverscanRamp = False
1241 self.config.doAddCrosstalk = False
1242 self.config.doAddBias = False
1243 self.config.doAdd2DBias = False
1244 self.config.doAddDark = False
1245 self.config.doApplyGain = doApplyGain
1246 self.config.doAddFlat = False
1247 self.config.doAddClockInjectedOffset = False
1248
1249 # Reference calibrations are not integerized.
1250 self.config.doRoundAdu = False
1251
1252
1253# Classes to generate calibration products mocks.
1255 """Simulated reference dark calibration.
1256 """
1257 def __init__(self, **kwargs):
1258 super().__init__(**kwargs)
1259 self.config.doAddDark = True
1260 self.config.darkTime = 1.0
1261
1262
1264 """Simulated combined bias calibration.
1265 """
1266 def __init__(self, **kwargs):
1267 super().__init__(**kwargs)
1268 # This is a "2D bias residual" frame which has only
1269 # the 2D bias in it.
1270 self.config.doAdd2DBias = True
1271
1272
1274 """Simulated reference flat calibration.
1275 """
1276 def __init__(self, **kwargs):
1277 super().__init__(**kwargs)
1278 self.config.doAddFlat = True
1279
1280
1282 """Simulated reference fringe calibration.
1283 """
1284 def __init__(self, **kwargs):
1285 super().__init__(**kwargs)
1286 self.config.doAddFringe = True
1287
1288
1290 """Simulated brighter-fatter kernel.
1291 """
1292 def __init__(self, **kwargs):
1293 super().__init__(**kwargs)
1294 self.config.doGenerateImage = False
1295 self.config.doGenerateData = True
1296
1297 self.config.doBrighterFatter = True
1298 self.config.doDefects = False
1299 self.config.doCrosstalkCoeffs = False
1300 self.config.doTransmissionCurve = False
1301 self.config.doLinearizer = False
1302
1303
1305 """Simulated deferred charge calibration.
1306 """
1307 def __init__(self, **kwargs):
1308 super().__init__(**kwargs)
1309 self.config.doGenerateImage = False
1310 self.config.doGenerateData = True
1311 self.config.doDeferredCharge = True
1312 self.config.doDefects = False
1313 self.config.doCrosstalkCoeffs = False
1314 self.config.doTransmissionCurve = False
1315
1316
1318 """Simulated defect list.
1319 """
1320 def __init__(self, **kwargs):
1321 super().__init__(**kwargs)
1322 self.config.doGenerateImage = False
1323 self.config.doGenerateData = True
1324
1325 self.config.doBrighterFatter = False
1326 self.config.doDefects = True
1327 self.config.doCrosstalkCoeffs = False
1328 self.config.doTransmissionCurve = False
1329 self.config.doLinearizer = False
1330
1331
1333 """Simulated crosstalk coefficient matrix.
1334 """
1335 def __init__(self, **kwargs):
1336 super().__init__(**kwargs)
1337 self.config.doGenerateImage = False
1338 self.config.doGenerateData = True
1339
1340 self.config.doBrighterFatter = False
1341 self.config.doDefects = False
1342 self.config.doCrosstalkCoeffs = True
1343 self.config.doTransmissionCurve = False
1344 self.config.doLinearizer = False
1345
1346
1348 """Simulated linearizer.
1349 """
1350 def __init__(self, **kwargs):
1351 super().__init__(**kwargs)
1352 self.config.doGenerateImage = False
1353 self.config.doGenerateData = True
1354
1355 self.config.doBrighterFatter = False
1356 self.config.doDefects = False
1357 self.config.doCrosstalkCoeffs = False
1358 self.config.doTransmissionCurve = False
1359 self.config.doLinearizer = True
1360
1361
1363 """Simulated transmission curve.
1364 """
1365 def __init__(self, **kwargs):
1366 super().__init__(**kwargs)
1367 self.config.doGenerateImage = False
1368 self.config.doGenerateData = True
1369
1370 self.config.doBrighterFatter = False
1371 self.config.doDefects = False
1372 self.config.doCrosstalkCoeffs = False
1373 self.config.doTransmissionCurve = True
1374 self.config.doLinearizer = False
Class for storing ordered metadata with comments.
An integer coordinate rectangle.
Definition Box.h:55
localCoordToExpCoord(self, ampData, x, y)
Definition isrMock.py:725
amplifierAddNoise(self, ampData, mean, sigma, rng=None)
Definition isrMock.py:756
amplifierAddSource(self, ampData, scale, x0, y0)
Definition isrMock.py:800
amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0)
Definition isrMock.py:875
amplifierAddYGradient(self, ampData, start, end)
Definition isrMock.py:781
getCamera(self, isForAssembly=False)
Definition isrMock.py:549
getExposure(self, isTrimmed=None)
Definition isrMock.py:576
amplifierAddCT(self, ampDataSource, ampDataTarget, scale)
Definition isrMock.py:821
amplifierAddFringe(self, amp, ampData, scale, x0=100, y0=0)
Definition isrMock.py:843
addBiasLevel(self, ampData, biasLevel)
amplifierAddDeferredCharge(self, exposure, amp)
makeDefectList(self, isTrimmed=True)
amplifierAddNonlinearity(self, ampData, centers, values, offset)
amplifierAddBrighterFatter(self, ampImageData, rng, bfStrength, nRecalc)
amplifierAddXGradient(self, ampData, start, end)
amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0)
Interpolate::Style stringToInterpStyle(std::string const &style)
Conversion function to switch a string to an Interpolate::Style.
std::shared_ptr< Interpolate > makeInterpolate(std::vector< double > const &x, std::vector< double > const &y, Interpolate::Style const style=Interpolate::AKIMA_SPLINE)
A factory function to make Interpolate objects.