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