LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
isrMock.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 import copy
23 import numpy as np
24 import tempfile
25 
26 import lsst.geom
27 import lsst.afw.geom as afwGeom
28 import lsst.afw.image as afwImage
29 import lsst.afw.math as afwMath
30 import lsst.afw.cameraGeom.utils as afwUtils
31 import lsst.afw.cameraGeom.testUtils as afwTestUtils
32 from lsst.meas.algorithms import Defects
33 import lsst.pex.config as pexConfig
34 import lsst.pipe.base as pipeBase
35 from .crosstalk import X_FLIP, Y_FLIP
36 
37 __all__ = ["IsrMockConfig", "IsrMock", "RawMock", "TrimmedRawMock", "RawDictMock",
38  "CalibratedRawMock", "MasterMock",
39  "BiasMock", "DarkMock", "FlatMock", "FringeMock", "UntrimmedFringeMock",
40  "BfKernelMock", "DefectMock", "CrosstalkCoeffMock", "TransmissionMock",
41  "DataRefMock"]
42 
43 
44 class IsrMockConfig(pexConfig.Config):
45  """Configuration parameters for isrMock.
46 
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.
51  """
52  # Detector parameters. "Exposure" parameters.
53  isLsstLike = pexConfig.Field(
54  dtype=bool,
55  default=False,
56  doc="If True, products have one raw image per amplifier, otherwise, one raw image per detector.",
57  )
58  plateScale = pexConfig.Field(
59  dtype=float,
60  default=20.0,
61  doc="Plate scale used in constructing mock camera.",
62  )
63  radialDistortion = pexConfig.Field(
64  dtype=float,
65  default=0.925,
66  doc="Radial distortion term used in constructing mock camera.",
67  )
68  isTrimmed = pexConfig.Field(
69  dtype=bool,
70  default=True,
71  doc="If True, amplifiers have been trimmed and mosaicked to remove regions outside the data BBox.",
72  )
73  detectorIndex = pexConfig.Field(
74  dtype=int,
75  default=20,
76  doc="Index for the detector to use. The default value uses a standard 2x4 array of amps.",
77  )
78  rngSeed = pexConfig.Field(
79  dtype=int,
80  default=20000913,
81  doc="Seed for random number generator used to add noise.",
82  )
83  # TODO: DM-18345 Check that mocks scale correctly when gain != 1.0
84  gain = pexConfig.Field(
85  dtype=float,
86  default=1.0,
87  doc="Gain for simulated data in e^-/DN.",
88  )
89  readNoise = pexConfig.Field(
90  dtype=float,
91  default=5.0,
92  doc="Read noise of the detector in e-.",
93  )
94  expTime = pexConfig.Field(
95  dtype=float,
96  default=5.0,
97  doc="Exposure time for simulated data.",
98  )
99 
100  # Signal parameters
101  skyLevel = pexConfig.Field(
102  dtype=float,
103  default=1000.0,
104  doc="Background contribution to be generated from 'the sky' in DN.",
105  )
106  sourceFlux = pexConfig.ListField(
107  dtype=float,
108  default=[45000.0],
109  doc="Peak flux level (in DN) of simulated 'astronomical sources'.",
110  )
111  sourceAmp = pexConfig.ListField(
112  dtype=int,
113  default=[0],
114  doc="Amplifier to place simulated 'astronomical sources'.",
115  )
116  sourceX = pexConfig.ListField(
117  dtype=float,
118  default=[50.0],
119  doc="Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
120  )
121  sourceY = pexConfig.ListField(
122  dtype=float,
123  default=[25.0],
124  doc="Peak position (in amplifier coordinates) of simulated 'astronomical sources'.",
125  )
126  overscanScale = pexConfig.Field(
127  dtype=float,
128  default=100.0,
129  doc="Amplitude (in DN) of the ramp function to add to overscan data.",
130  )
131  biasLevel = pexConfig.Field(
132  dtype=float,
133  default=8000.0,
134  doc="Background contribution to be generated from the bias offset in DN.",
135  )
136  darkRate = pexConfig.Field(
137  dtype=float,
138  default=5.0,
139  doc="Background level contribution (in e-/s) to be generated from dark current.",
140  )
141  darkTime = pexConfig.Field(
142  dtype=float,
143  default=5.0,
144  doc="Exposure time for the dark current contribution.",
145  )
146  flatDrop = pexConfig.Field(
147  dtype=float,
148  default=0.1,
149  doc="Fractional flux drop due to flat from center to edge of detector along x-axis.",
150  )
151  fringeScale = pexConfig.ListField(
152  dtype=float,
153  default=[200.0],
154  doc="Peak fluxes for the components of the fringe ripple in DN.",
155  )
156  fringeX0 = pexConfig.ListField(
157  dtype=float,
158  default=[-100],
159  doc="Center position for the fringe ripples.",
160  )
161  fringeY0 = pexConfig.ListField(
162  dtype=float,
163  default=[-0],
164  doc="Center position for the fringe ripples.",
165  )
166 
167  # Inclusion parameters
168  doAddSky = pexConfig.Field(
169  dtype=bool,
170  default=True,
171  doc="Apply 'sky' signal to output image.",
172  )
173  doAddSource = pexConfig.Field(
174  dtype=bool,
175  default=True,
176  doc="Add simulated source to output image.",
177  )
178  doAddCrosstalk = pexConfig.Field(
179  dtype=bool,
180  default=False,
181  doc="Apply simulated crosstalk to output image. This cannot be corrected by ISR, "
182  "as detector.hasCrosstalk()==False.",
183  )
184  doAddOverscan = pexConfig.Field(
185  dtype=bool,
186  default=True,
187  doc="If untrimmed, add overscan ramp to overscan and data regions.",
188  )
189  doAddBias = pexConfig.Field(
190  dtype=bool,
191  default=True,
192  doc="Add bias signal to data.",
193  )
194  doAddDark = pexConfig.Field(
195  dtype=bool,
196  default=True,
197  doc="Add dark signal to data.",
198  )
199  doAddFlat = pexConfig.Field(
200  dtype=bool,
201  default=True,
202  doc="Add flat signal to data.",
203  )
204  doAddFringe = pexConfig.Field(
205  dtype=bool,
206  default=True,
207  doc="Add fringe signal to data.",
208  )
209 
210  # Datasets to create and return instead of generating an image.
211  doTransmissionCurve = pexConfig.Field(
212  dtype=bool,
213  default=False,
214  doc="Return a simulated transmission curve.",
215  )
216  doDefects = pexConfig.Field(
217  dtype=bool,
218  default=False,
219  doc="Return a simulated defect list.",
220  )
221  doBrighterFatter = pexConfig.Field(
222  dtype=bool,
223  default=False,
224  doc="Return a simulated brighter-fatter kernel.",
225  )
226  doCrosstalkCoeffs = pexConfig.Field(
227  dtype=bool,
228  default=False,
229  doc="Return the matrix of crosstalk coefficients.",
230  )
231  doDataRef = pexConfig.Field(
232  dtype=bool,
233  default=False,
234  doc="Return a simulated gen2 butler dataRef.",
235  )
236  doGenerateImage = pexConfig.Field(
237  dtype=bool,
238  default=False,
239  doc="Return the generated output image if True.",
240  )
241  doGenerateData = pexConfig.Field(
242  dtype=bool,
243  default=False,
244  doc="Return a non-image data structure if True.",
245  )
246  doGenerateAmpDict = pexConfig.Field(
247  dtype=bool,
248  default=False,
249  doc="Return a dict of exposure amplifiers instead of an afwImage.Exposure.",
250  )
251 
252 
253 class IsrMock(pipeBase.Task):
254  """Class to generate consistent mock images for ISR testing.
255 
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.
261  """
262  ConfigClass = IsrMockConfig
263  _DefaultName = "isrMock"
264 
265  def __init__(self, **kwargs):
266  super().__init__(**kwargs)
267  self.rng = np.random.RandomState(self.config.rngSeed)
268  self.crosstalkCoeffs = np.array([[0.0, 0.0, 0.0, 0.0, 0.0, -1e-3, 0.0, 0.0],
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]])
276 
277  self.bfKernel = np.array([[1., 4., 7., 4., 1.],
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
282 
283  def run(self):
284  """Generate a mock ISR product, and return it.
285 
286  Returns
287  -------
288  image : `lsst.afw.image.Exposure`
289  Simulated ISR image with signals added.
290  dataProduct :
291  Simulated ISR data products.
292  None :
293  Returned if no valid configuration was found.
294 
295  Raises
296  ------
297  RuntimeError
298  Raised if both doGenerateImage and doGenerateData are specified.
299  """
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:
303  return self.makeImage()
304  elif self.config.doGenerateData:
305  return self.makeData()
306  else:
307  return None
308 
309  def makeData(self):
310  """Generate simulated ISR data.
311 
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.
315 
316  Returns
317  -------
318  dataProduct :
319  Simulated ISR data product.
320  """
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:
327  return self.makeBfKernel()
328  elif self.config.doDefects is True:
329  return self.makeDefectList()
330  elif self.config.doTransmissionCurve is True:
331  return self.makeTransmissionCurve()
332  elif self.config.doCrosstalkCoeffs is True:
333  return self.crosstalkCoeffs
334  else:
335  return None
336 
337  def makeBfKernel(self):
338  """Generate a simple Gaussian brighter-fatter kernel.
339 
340  Returns
341  -------
342  kernel : `numpy.ndarray`
343  Simulated brighter-fatter kernel.
344  """
345  return self.bfKernel
346 
347  def makeDefectList(self):
348  """Generate a simple single-entry defect list.
349 
350  Returns
351  -------
352  defectList : `lsst.meas.algorithms.Defects`
353  Simulated defect list
354  """
356  lsst.geom.Extent2I(40, 50))])
357 
359  """Generate the simulated crosstalk coefficients.
360 
361  Returns
362  -------
363  coeffs : `numpy.ndarray`
364  Simulated crosstalk coefficients.
365  """
366 
367  return self.crosstalkCoeffs
368 
370  """Generate a simulated flat transmission curve.
371 
372  Returns
373  -------
374  transmission : `lsst.afw.image.TransmissionCurve`
375  Simulated transmission curve.
376  """
377 
378  return afwImage.TransmissionCurve.makeIdentity()
379 
380  def makeImage(self):
381  """Generate a simulated ISR image.
382 
383  Returns
384  -------
385  exposure : `lsst.afw.image.Exposure` or `dict`
386  Simulated ISR image data.
387 
388  Notes
389  -----
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.
400 
401  The exposure with image data constructed this way is in one of
402  three formats.
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
406  amplifier name.
407 
408  The nonlinearity, CTE, and brighter fatter are currently not
409  implemented.
410 
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
414  exposure.
415  """
416  exposure = self.getExposure()
417 
418  for idx, amp in enumerate(exposure.getDetector()):
419  bbox = None
420  if self.config.isTrimmed is True:
421  bbox = amp.getBBox()
422  else:
423  bbox = amp.getRawDataBBox()
424 
425  ampData = exposure.image[bbox]
426 
427  if self.config.doAddSky is True:
428  self.amplifierAddNoise(ampData, self.config.skyLevel, np.sqrt(self.config.skyLevel))
429 
430  if self.config.doAddSource is True:
431  for sourceAmp, sourceFlux, sourceX, sourceY in zip(self.config.sourceAmp,
432  self.config.sourceFlux,
433  self.config.sourceX,
434  self.config.sourceY):
435  if idx == sourceAmp:
436  self.amplifierAddSource(ampData, sourceFlux, sourceX, sourceY)
437 
438  if self.config.doAddFringe is True:
439  self.amplifierAddFringe(amp, ampData, np.array(self.config.fringeScale),
440  x0=np.array(self.config.fringeX0),
441  y0=np.array(self.config.fringeY0))
442 
443  if self.config.doAddFlat is True:
444  if ampData.getArray().sum() == 0.0:
445  self.amplifierAddNoise(ampData, 1.0, 0.0)
446  u0 = exposure.getDimensions().getX()
447  v0 = exposure.getDimensions().getY()
448  self.amplifierMultiplyFlat(amp, ampData, self.config.flatDrop, u0=u0, v0=v0)
449 
450  if self.config.doAddDark is True:
451  self.amplifierAddNoise(ampData,
452  self.config.darkRate * self.config.darkTime / self.config.gain,
453  np.sqrt(self.config.darkRate *
454  self.config.darkTime / self.config.gain))
455 
456  if self.config.doAddCrosstalk is True:
457 
458  for idxS, ampS in enumerate(exposure.getDetector()):
459  for idxT, ampT in enumerate(exposure.getDetector()):
460  ampDataS = exposure.image[ampS.getBBox() if self.config.isTrimmed
461  else ampS.getRawDataBBox()]
462  ampDataT = exposure.image[ampT.getBBox() if self.config.isTrimmed
463  else ampT.getRawDataBBox()]
464  ampDataS = afwMath.flipImage(ampDataS,
465  (X_FLIP[ampS.getReadoutCorner()] ^
466  X_FLIP[ampT.getReadoutCorner()]),
467  (Y_FLIP[ampS.getReadoutCorner()] ^
468  Y_FLIP[ampT.getReadoutCorner()]))
469  self.amplifierAddCT(ampDataS, ampDataT, self.crosstalkCoeffs[idxT][idxS])
470 
471  for amp in exposure.getDetector():
472  bbox = None
473  if self.config.isTrimmed is True:
474  bbox = amp.getBBox()
475  else:
476  bbox = amp.getRawDataBBox()
477 
478  ampData = exposure.image[bbox]
479 
480  if self.config.doAddBias is True:
481  self.amplifierAddNoise(ampData, self.config.biasLevel,
482  self.config.readNoise / self.config.gain)
483 
484  if self.config.doAddOverscan is True:
485  oscanBBox = amp.getRawHorizontalOverscanBBox()
486  oscanData = exposure.image[oscanBBox]
487  self.amplifierAddNoise(oscanData, self.config.biasLevel,
488  self.config.readNoise / self.config.gain)
489 
490  self.amplifierAddYGradient(ampData, -1.0 * self.config.overscanScale,
491  1.0 * self.config.overscanScale)
492  self.amplifierAddYGradient(oscanData, -1.0 * self.config.overscanScale,
493  1.0 * self.config.overscanScale)
494 
495  if self.config.doGenerateAmpDict is True:
496  expDict = dict()
497  for amp in exposure.getDetector():
498  expDict[amp.getName()] = exposure
499  return expDict
500  else:
501  return exposure
502 
503  # afw primatives to construct the image structure
504  def getCamera(self):
505  """Construct a test camera object.
506 
507  Returns
508  -------
509  camera : `lsst.afw.cameraGeom.camera`
510  Test camera.
511  """
512  cameraWrapper = afwTestUtils.CameraWrapper(
513  plateScale=self.config.plateScale,
514  radialDistortion=self.config.radialDistortion,
515  isLsstLike=self.config.isLsstLike,
516  )
517  camera = cameraWrapper.camera
518  return camera
519 
520  def getExposure(self):
521  """Construct a test exposure.
522 
523  The test exposure has a simple WCS set, as well as a list of
524  unlikely header keywords that can be removed during ISR
525  processing to exercise that code.
526 
527  Returns
528  -------
529  exposure : `lsst.afw.exposure.Exposure`
530  Construct exposure containing masked image of the
531  appropriate size.
532  """
533  camera = self.getCamera()
534  detector = camera[self.config.detectorIndex]
535  image = afwUtils.makeImageFromCcd(detector,
536  isTrimmed=self.config.isTrimmed,
537  showAmpGain=False,
538  rcMarkSize=0,
539  binSize=1,
540  imageFactory=afwImage.ImageF)
541 
542  var = afwImage.ImageF(image.getDimensions())
543  mask = afwImage.Mask(image.getDimensions())
544  image.assign(0.0)
545 
546  maskedImage = afwImage.makeMaskedImage(image, mask, var)
547  exposure = afwImage.makeExposure(maskedImage)
548  exposure.setDetector(detector)
549  exposure.setWcs(self.getWcs())
550 
551  visitInfo = afwImage.VisitInfo(exposureTime=self.config.expTime, darkTime=self.config.darkTime)
552  exposure.getInfo().setVisitInfo(visitInfo)
553 
554  metadata = exposure.getMetadata()
555  metadata.add("SHEEP", 7.3, "number of sheep on farm")
556  metadata.add("MONKEYS", 155, "monkeys per tree")
557  metadata.add("VAMPIRES", 4, "How scary are vampires.")
558 
559  ccd = exposure.getDetector()
560  newCcd = ccd.rebuild()
561  newCcd.clear()
562  for amp in ccd:
563  newAmp = amp.rebuild()
564  newAmp.setLinearityCoeffs((0., 1., 0., 0.))
565  newAmp.setLinearityType("Polynomial")
566  newAmp.setGain(self.config.gain)
567  newAmp.setSuspectLevel(25000.0)
568  newAmp.setSaturation(32000.0)
569  newCcd.append(newAmp)
570  exposure.setDetector(newCcd.finish())
571 
572  exposure.image.array[:] = np.zeros(exposure.getImage().getDimensions()).transpose()
573  exposure.mask.array[:] = np.zeros(exposure.getMask().getDimensions()).transpose()
574  exposure.variance.array[:] = np.zeros(exposure.getVariance().getDimensions()).transpose()
575 
576  return exposure
577 
578  def getWcs(self):
579  """Construct a dummy WCS object.
580 
581  Taken from the deprecated ip_isr/examples/exampleUtils.py.
582 
583  This is not guaranteed, given the distortion and pixel scale
584  listed in the afwTestUtils camera definition.
585 
586  Returns
587  -------
588  wcs : `lsst.afw.geom.SkyWcs`
589  Test WCS transform.
590  """
591  return afwGeom.makeSkyWcs(crpix=lsst.geom.Point2D(0.0, 100.0),
592  crval=lsst.geom.SpherePoint(45.0, 25.0, lsst.geom.degrees),
593  cdMatrix=afwGeom.makeCdMatrix(scale=1.0*lsst.geom.degrees))
594 
595  def localCoordToExpCoord(self, ampData, x, y):
596  """Convert between a local amplifier coordinate and the full
597  exposure coordinate.
598 
599  Parameters
600  ----------
601  ampData : `lsst.afw.image.ImageF`
602  Amplifier image to use for conversions.
603  x : `int`
604  X-coordinate of the point to transform.
605  y : `int`
606  Y-coordinate of the point to transform.
607 
608  Returns
609  -------
610  u : `int`
611  Transformed x-coordinate.
612  v : `int`
613  Transformed y-coordinate.
614 
615  Notes
616  -----
617  The output is transposed intentionally here, to match the
618  internal transpose between numpy and afw.image coordinates.
619  """
620  u = x + ampData.getBBox().getBeginX()
621  v = y + ampData.getBBox().getBeginY()
622 
623  return (v, u)
624 
625  # Simple data values.
626  def amplifierAddNoise(self, ampData, mean, sigma):
627  """Add Gaussian noise to an amplifier's image data.
628 
629  This method operates in the amplifier coordinate frame.
630 
631  Parameters
632  ----------
633  ampData : `lsst.afw.image.ImageF`
634  Amplifier image to operate on.
635  mean : `float`
636  Mean value of the Gaussian noise.
637  sigma : `float`
638  Sigma of the Gaussian noise.
639  """
640  ampArr = ampData.array
641  ampArr[:] = ampArr[:] + self.rng.normal(mean, sigma,
642  size=ampData.getDimensions()).transpose()
643 
644  def amplifierAddYGradient(self, ampData, start, end):
645  """Add a y-axis linear gradient to an amplifier's image data.
646 
647  This method operates in the amplifier coordinate frame.
648 
649  Parameters
650  ----------
651  ampData : `lsst.afw.image.ImageF`
652  Amplifier image to operate on.
653  start : `float`
654  Start value of the gradient (at y=0).
655  end : `float`
656  End value of the gradient (at y=ymax).
657  """
658  nPixY = ampData.getDimensions().getY()
659  ampArr = ampData.array
660  ampArr[:] = ampArr[:] + (np.interp(range(nPixY), (0, nPixY - 1), (start, end)).reshape(nPixY, 1) +
661  np.zeros(ampData.getDimensions()).transpose())
662 
663  def amplifierAddSource(self, ampData, scale, x0, y0):
664  """Add a single Gaussian source to an amplifier.
665 
666  This method operates in the amplifier coordinate frame.
667 
668  Parameters
669  ----------
670  ampData : `lsst.afw.image.ImageF`
671  Amplifier image to operate on.
672  scale : `float`
673  Peak flux of the source to add.
674  x0 : `float`
675  X-coordinate of the source peak.
676  y0 : `float`
677  Y-coordinate of the source peak.
678  """
679  for x in range(0, ampData.getDimensions().getX()):
680  for y in range(0, ampData.getDimensions().getY()):
681  ampData.array[y][x] = (ampData.array[y][x] +
682  scale * np.exp(-0.5 * ((x - x0)**2 + (y - y0)**2) / 3.0**2))
683 
684  def amplifierAddCT(self, ampDataSource, ampDataTarget, scale):
685  """Add a scaled copy of an amplifier to another, simulating crosstalk.
686 
687  This method operates in the amplifier coordinate frame.
688 
689  Parameters
690  ----------
691  ampDataSource : `lsst.afw.image.ImageF`
692  Amplifier image to add scaled copy from.
693  ampDataTarget : `lsst.afw.image.ImageF`
694  Amplifier image to add scaled copy to.
695  scale : `float`
696  Flux scale of the copy to add to the target.
697 
698  Notes
699  -----
700  This simulates simple crosstalk between amplifiers.
701  """
702  ampDataTarget.array[:] = (ampDataTarget.array[:] +
703  scale * ampDataSource.array[:])
704 
705  # Functional form data values.
706  def amplifierAddFringe(self, amp, ampData, scale, x0=100, y0=0):
707  """Add a fringe-like ripple pattern to an amplifier's image data.
708 
709  Parameters
710  ----------
711  amp : `~lsst.afw.ampInfo.AmpInfoRecord`
712  Amplifier to operate on. Needed for amp<->exp coordinate transforms.
713  ampData : `lsst.afw.image.ImageF`
714  Amplifier image to operate on.
715  scale : `numpy.array` or `float`
716  Peak intensity scaling for the ripple.
717  x0 : `numpy.array` or `float`, optional
718  Fringe center
719  y0 : `numpy.array` or `float`, optional
720  Fringe center
721 
722  Notes
723  -----
724  This uses an offset sinc function to generate a ripple
725  pattern. True fringes have much finer structure, but this
726  pattern should be visually identifiable. The (x, y)
727  coordinates are in the frame of the amplifier, and (u, v) in
728  the frame of the full trimmed image.
729  """
730  for x in range(0, ampData.getDimensions().getX()):
731  for y in range(0, ampData.getDimensions().getY()):
732  (u, v) = self.localCoordToExpCoord(amp, x, y)
733  ampData.getArray()[y][x] = np.sum((ampData.getArray()[y][x] +
734  scale *
735  np.sinc(((u - x0) / 50)**2 +
736  ((v - y0) / 50)**2)))
737 
738  def amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0):
739  """Multiply an amplifier's image data by a flat-like pattern.
740 
741  Parameters
742  ----------
743  amp : `lsst.afw.ampInfo.AmpInfoRecord`
744  Amplifier to operate on. Needed for amp<->exp coordinate transforms.
745  ampData : `lsst.afw.image.ImageF`
746  Amplifier image to operate on.
747  fracDrop : `float`
748  Fractional drop from center to edge of detector along x-axis.
749  u0 : `float`
750  Peak location in detector coordinates.
751  v0 : `float`
752  Peak location in detector coordinates.
753 
754  Notes
755  -----
756  This uses a 2-d Gaussian to simulate an illumination pattern
757  that falls off towards the edge of the detector. The (x, y)
758  coordinates are in the frame of the amplifier, and (u, v) in
759  the frame of the full trimmed image.
760  """
761  if fracDrop >= 1.0:
762  raise RuntimeError("Flat fractional drop cannot be greater than 1.0")
763 
764  sigma = u0 / np.sqrt(-2.0 * np.log(fracDrop))
765 
766  for x in range(0, ampData.getDimensions().getX()):
767  for y in range(0, ampData.getDimensions().getY()):
768  (u, v) = self.localCoordToExpCoord(amp, x, y)
769  f = np.exp(-0.5 * ((u - u0)**2 + (v - v0)**2) / sigma**2)
770  ampData.array[y][x] = (ampData.array[y][x] * f)
771 
772 
774  """Generate a raw exposure suitable for ISR.
775  """
776  def __init__(self, **kwargs):
777  super().__init__(**kwargs)
778  self.config.isTrimmed = False
779  self.config.doGenerateImage = True
780  self.config.doGenerateAmpDict = False
781  self.config.doAddOverscan = True
782  self.config.doAddSky = True
783  self.config.doAddSource = True
784  self.config.doAddCrosstalk = False
785  self.config.doAddBias = True
786  self.config.doAddDark = True
787 
788 
790  """Generate a trimmed raw exposure.
791  """
792  def __init__(self, **kwargs):
793  super().__init__(**kwargs)
794  self.config.isTrimmed = True
795  self.config.doAddOverscan = False
796 
797 
799  """Generate a trimmed raw exposure.
800  """
801  def __init__(self, **kwargs):
802  super().__init__(**kwargs)
803  self.config.isTrimmed = True
804  self.config.doGenerateImage = True
805  self.config.doAddOverscan = False
806  self.config.doAddSky = True
807  self.config.doAddSource = True
808  self.config.doAddCrosstalk = False
809 
810  self.config.doAddBias = False
811  self.config.doAddDark = False
812  self.config.doAddFlat = False
813  self.config.doAddFringe = True
814 
815  self.config.biasLevel = 0.0
816  self.config.readNoise = 10.0
817 
818 
820  """Generate a raw exposure dict suitable for ISR.
821  """
822  def __init__(self, **kwargs):
823  super().__init__(**kwargs)
824  self.config.doGenerateAmpDict = True
825 
826 
828  """Parent class for those that make master calibrations.
829  """
830  def __init__(self, **kwargs):
831  super().__init__(**kwargs)
832  self.config.isTrimmed = True
833  self.config.doGenerateImage = True
834  self.config.doAddOverscan = False
835  self.config.doAddSky = False
836  self.config.doAddSource = False
837  self.config.doAddCrosstalk = False
838 
839  self.config.doAddBias = False
840  self.config.doAddDark = False
841  self.config.doAddFlat = False
842  self.config.doAddFringe = False
843 
844 
846  """Simulated master bias calibration.
847  """
848  def __init__(self, **kwargs):
849  super().__init__(**kwargs)
850  self.config.doAddBias = True
851  self.config.readNoise = 10.0
852 
853 
855  """Simulated master dark calibration.
856  """
857  def __init__(self, **kwargs):
858  super().__init__(**kwargs)
859  self.config.doAddDark = True
860  self.config.darkTime = 1.0
861 
862 
864  """Simulated master flat calibration.
865  """
866  def __init__(self, **kwargs):
867  super().__init__(**kwargs)
868  self.config.doAddFlat = True
869 
870 
872  """Simulated master fringe calibration.
873  """
874  def __init__(self, **kwargs):
875  super().__init__(**kwargs)
876  self.config.doAddFringe = True
877 
878 
880  """Simulated untrimmed master fringe calibration.
881  """
882  def __init__(self, **kwargs):
883  super().__init__(**kwargs)
884  self.config.isTrimmed = False
885 
886 
888  """Simulated brighter-fatter kernel.
889  """
890  def __init__(self, **kwargs):
891  super().__init__(**kwargs)
892  self.config.doGenerateImage = False
893  self.config.doGenerateData = True
894  self.config.doBrighterFatter = True
895  self.config.doDefects = False
896  self.config.doCrosstalkCoeffs = False
897  self.config.doTransmissionCurve = False
898 
899 
901  """Simulated defect list.
902  """
903  def __init__(self, **kwargs):
904  super().__init__(**kwargs)
905  self.config.doGenerateImage = False
906  self.config.doGenerateData = True
907  self.config.doBrighterFatter = False
908  self.config.doDefects = True
909  self.config.doCrosstalkCoeffs = False
910  self.config.doTransmissionCurve = False
911 
912 
914  """Simulated crosstalk coefficient matrix.
915  """
916  def __init__(self, **kwargs):
917  super().__init__(**kwargs)
918  self.config.doGenerateImage = False
919  self.config.doGenerateData = True
920  self.config.doBrighterFatter = False
921  self.config.doDefects = False
922  self.config.doCrosstalkCoeffs = True
923  self.config.doTransmissionCurve = False
924 
925 
927  """Simulated transmission curve.
928  """
929  def __init__(self, **kwargs):
930  super().__init__(**kwargs)
931  self.config.doGenerateImage = False
932  self.config.doGenerateData = True
933  self.config.doBrighterFatter = False
934  self.config.doDefects = False
935  self.config.doCrosstalkCoeffs = False
936  self.config.doTransmissionCurve = True
937 
938 
940  """Simulated gen2 butler data ref.
941 
942  Currently only supports get and put operations, which are most
943  likely to be called for data in ISR processing.
944 
945  """
946  dataId = "isrMock Fake Data"
947  darkval = 2. # e-/sec
948  oscan = 250. # DN
949  gradient = .10
950  exptime = 15.0 # seconds
951  darkexptime = 15.0 # seconds
952 
953  def __init__(self, **kwargs):
954  if 'config' in kwargs.keys():
955  self.config = kwargs['config']
956  else:
957  self.config = None
958 
959  def expectImage(self):
960  if self.config is None:
961  self.config = IsrMockConfig()
962  self.config.doGenerateImage = True
963  self.config.doGenerateData = False
964 
965  def expectData(self):
966  if self.config is None:
967  self.config = IsrMockConfig()
968  self.config.doGenerateImage = False
969  self.config.doGenerateData = True
970 
971  def get(self, dataType, **kwargs):
972  """Return an appropriate data product.
973 
974  Parameters
975  ----------
976  dataType : `str`
977  Type of data product to return.
978 
979  Returns
980  -------
981  mock : IsrMock.run() result
982  The output product.
983  """
984  if "_filename" in dataType:
985  self.expectData()
986  return tempfile.mktemp(), "mock"
987  elif 'transmission_' in dataType:
988  self.expectData()
989  return TransmissionMock(config=self.config).run()
990  elif dataType == 'ccdExposureId':
991  self.expectData()
992  return 20090913
993  elif dataType == 'camera':
994  self.expectData()
995  return IsrMock(config=self.config).getCamera()
996  elif dataType == 'raw':
997  self.expectImage()
998  return RawMock(config=self.config).run()
999  elif dataType == 'bias':
1000  self.expectImage()
1001  return BiasMock(config=self.config).run()
1002  elif dataType == 'dark':
1003  self.expectImage()
1004  return DarkMock(config=self.config).run()
1005  elif dataType == 'flat':
1006  self.expectImage()
1007  return FlatMock(config=self.config).run()
1008  elif dataType == 'fringe':
1009  self.expectImage()
1010  return FringeMock(config=self.config).run()
1011  elif dataType == 'defects':
1012  self.expectData()
1013  return DefectMock(config=self.config).run()
1014  elif dataType == 'bfKernel':
1015  self.expectData()
1016  return BfKernelMock(config=self.config).run()
1017  elif dataType == 'linearizer':
1018  return None
1019  elif dataType == 'crosstalkSources':
1020  return None
1021  else:
1022  raise RuntimeError("ISR DataRefMock cannot return %s.", dataType)
1023 
1024  def put(self, exposure, filename):
1025  """Write an exposure to a FITS file.
1026 
1027  Parameters
1028  ----------
1029  exposure : `lsst.afw.image.Exposure`
1030  Image data to write out.
1031  filename : `str`
1032  Base name of the output file.
1033  """
1034  exposure.writeFits(filename+".fits")
1035 
1036 
1038  """Simulated gen2 butler data ref.
1039 
1040  Currently only supports get and put operations, which are most
1041  likely to be called for data in ISR processing.
1042 
1043  """
1044  dataId = "isrMock Fake Data"
1045  darkval = 2. # e-/sec
1046  oscan = 250. # DN
1047  gradient = .10
1048  exptime = 15 # seconds
1049  darkexptime = 40. # seconds
1050 
1051  def __init__(self, **kwargs):
1052  if 'config' in kwargs.keys():
1053  self.config = kwargs['config']
1054  else:
1055  self.config = IsrMockConfig()
1056  self.config.isTrimmed = True
1057  self.config.doAddFringe = True
1058  self.config.readNoise = 10.0
1059 
1060  def get(self, dataType, **kwargs):
1061  """Return an appropriate data product.
1062 
1063  Parameters
1064  ----------
1065  dataType : `str`
1066  Type of data product to return.
1067 
1068  Returns
1069  -------
1070  mock : IsrMock.run() result
1071  The output product.
1072  """
1073  if "_filename" in dataType:
1074  return tempfile.mktemp(), "mock"
1075  elif 'transmission_' in dataType:
1076  return TransmissionMock(config=self.config).run()
1077  elif dataType == 'ccdExposureId':
1078  return 20090913
1079  elif dataType == 'camera':
1080  return IsrMock(config=self.config).getCamera()
1081  elif dataType == 'raw':
1082  return CalibratedRawMock(config=self.config).run()
1083  elif dataType == 'bias':
1084  return BiasMock(config=self.config).run()
1085  elif dataType == 'dark':
1086  return DarkMock(config=self.config).run()
1087  elif dataType == 'flat':
1088  return FlatMock(config=self.config).run()
1089  elif dataType == 'fringe':
1090  fringes = []
1091  configCopy = copy.deepcopy(self.config)
1092  for scale, x, y in zip(self.config.fringeScale, self.config.fringeX0, self.config.fringeY0):
1093  configCopy.fringeScale = [1.0]
1094  configCopy.fringeX0 = [x]
1095  configCopy.fringeY0 = [y]
1096  fringes.append(FringeMock(config=configCopy).run())
1097  return fringes
1098  elif dataType == 'defects':
1099  return DefectMock(config=self.config).run()
1100  elif dataType == 'bfKernel':
1101  return BfKernelMock(config=self.config).run()
1102  elif dataType == 'linearizer':
1103  return None
1104  elif dataType == 'crosstalkSources':
1105  return None
1106  else:
1107  return None
1108 
1109  def put(self, exposure, filename):
1110  """Write an exposure to a FITS file.
1111 
1112  Parameters
1113  ----------
1114  exposure : `lsst.afw.image.Exposure`
1115  Image data to write out.
1116  filename : `str`
1117  Base name of the output file.
1118  """
1119  exposure.writeFits(filename+".fits")
def amplifierAddNoise(self, ampData, mean, sigma)
Definition: isrMock.py:626
def __init__(self, kwargs)
Definition: isrMock.py:866
def __init__(self, kwargs)
Definition: isrMock.py:776
def amplifierAddSource(self, ampData, scale, x0, y0)
Definition: isrMock.py:663
def __init__(self, kwargs)
Definition: isrMock.py:265
std::shared_ptr< ImageT > flipImage(ImageT const &inImage, bool flipLR, bool flipTB)
Flip an image left–right and/or top–bottom.
Definition: rotateImage.cc:92
def __init__(self, kwargs)
Definition: isrMock.py:830
def amplifierAddYGradient(self, ampData, start, end)
Definition: isrMock.py:644
def put(self, exposure, filename)
Definition: isrMock.py:1024
Information about a single exposure of an imaging camera.
Definition: VisitInfo.h:68
def get(self, dataType, kwargs)
Definition: isrMock.py:1060
def amplifierAddFringe(self, amp, ampData, scale, x0=100, y0=0)
Definition: isrMock.py:706
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT >> image, typename std::shared_ptr< Mask< MaskPixelT >> mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT >> variance=Image< VariancePixelT >())
A function to return a MaskedImage of the correct type (cf.
Definition: MaskedImage.h:1279
def __init__(self, kwargs)
Definition: isrMock.py:953
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
A function to return an Exposure of the correct type (cf.
Definition: Exposure.h:442
def __init__(self, kwargs)
Definition: isrMock.py:822
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
def localCoordToExpCoord(self, ampData, x, y)
Definition: isrMock.py:595
def amplifierMultiplyFlat(self, amp, ampData, fracDrop, u0=100.0, v0=100.0)
Definition: isrMock.py:738
def __init__(self, kwargs)
Definition: isrMock.py:903
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
def get(self, dataType, kwargs)
Definition: isrMock.py:971
def put(self, exposure, filename)
Definition: isrMock.py:1109
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
def __init__(self, kwargs)
Definition: isrMock.py:890
std::shared_ptr< SkyWcs > makeSkyWcs(TransformPoint2ToPoint2 const &pixelsToFieldAngle, lsst::geom::Angle const &orientation, bool flipX, lsst::geom::SpherePoint const &boresight, std::string const &projection="TAN")
Construct a FITS SkyWcs from camera geometry.
Definition: SkyWcs.cc:516
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
Make a WCS CD matrix.
Definition: SkyWcs.cc:138
def __init__(self, kwargs)
Definition: isrMock.py:857
def __init__(self, kwargs)
Definition: isrMock.py:874
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def amplifierAddCT(self, ampDataSource, ampDataTarget, scale)
Definition: isrMock.py:684
An integer coordinate rectangle.
Definition: Box.h:55
def __init__(self, kwargs)
Definition: isrMock.py:848