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