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