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