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