LSST Applications  21.0.0-131-g8cabc107+528f53ee53,22.0.0+00495a2688,22.0.0+0ef2527977,22.0.0+11a2aa21cd,22.0.0+269b7e55e3,22.0.0+2c6b6677a3,22.0.0+64c1bc5aa5,22.0.0+7b3a3f865e,22.0.0+e1b6d2281c,22.0.0+ff3c34362c,22.0.1-1-g1b65d06+c95cbdf3df,22.0.1-1-g7058be7+1cf78af69b,22.0.1-1-g7dab645+2a65e40b06,22.0.1-1-g8760c09+64c1bc5aa5,22.0.1-1-g949febb+64c1bc5aa5,22.0.1-1-ga324b9c+269b7e55e3,22.0.1-1-gf9d8b05+ff3c34362c,22.0.1-10-g781e53d+9b51d1cd24,22.0.1-10-gba590ab+b9624b875d,22.0.1-13-g76f9b8d+2c6b6677a3,22.0.1-14-g22236948+57af756299,22.0.1-18-g3db9cf4b+9b7092c56c,22.0.1-18-gb17765a+2264247a6b,22.0.1-2-g8ef0a89+2c6b6677a3,22.0.1-2-gcb770ba+c99495d3c6,22.0.1-24-g2e899d296+4206820b0d,22.0.1-3-g7aa11f2+2c6b6677a3,22.0.1-3-g8c1d971+f253ffa91f,22.0.1-3-g997b569+ff3b2f8649,22.0.1-4-g1930a60+6871d0c7f6,22.0.1-4-g5b7b756+6b209d634c,22.0.1-6-ga02864e+6871d0c7f6,22.0.1-7-g3402376+a1a2182ac4,22.0.1-7-g65f59fa+54b92689ce,master-gcc5351303a+e1b6d2281c,w.2021.32
LSST Data Management Base Package
Public Member Functions | Static Public Attributes | List of all members
lsst.cp.pipe.cpCombine.CalibCombineByFilterTask Class Reference
Inheritance diagram for lsst.cp.pipe.cpCombine.CalibCombineByFilterTask:
lsst.cp.pipe.cpCombine.CalibCombineTask

Public Member Functions

def runQuantum (self, butlerQC, inputRefs, outputRefs)
 
def run (self, inputExps, inputScales=None, inputDims=None)
 
def getDimensions (self, expList)
 
def getSize (self, dimList)
 
def applyScale (self, exposure, scale=None)
 
def combine (self, target, expList, stats)
 
def combineHeaders (self, expList, calib, calibType="CALIB", scales=None)
 
def interpolateNans (self, exp)
 

Static Public Attributes

 ConfigClass = CalibCombineByFilterConfig
 

Detailed Description

Task to combine calib exposures.

Definition at line 551 of file cpCombine.py.

Member Function Documentation

◆ applyScale()

def lsst.cp.pipe.cpCombine.CalibCombineTask.applyScale (   self,
  exposure,
  scale = None 
)
inherited
Apply scale to input exposure.

This implementation applies a flux scaling: the input exposure is
divided by the provided scale.

Parameters
----------
exposure : `lsst.afw.image.Exposure`
    Exposure to scale.
scale : `float` or `list` [`float`], optional
    Constant scale to divide the exposure by.

Definition at line 381 of file cpCombine.py.

381  def applyScale(self, exposure, scale=None):
382  """Apply scale to input exposure.
383 
384  This implementation applies a flux scaling: the input exposure is
385  divided by the provided scale.
386 
387  Parameters
388  ----------
389  exposure : `lsst.afw.image.Exposure`
390  Exposure to scale.
391  scale : `float` or `list` [`float`], optional
392  Constant scale to divide the exposure by.
393  """
394  if scale is not None:
395  mi = exposure.getMaskedImage()
396  if isinstance(scale, list):
397  for amp, ampScale in zip(exposure.getDetector(), scale):
398  ampIm = mi[amp.getBBox()]
399  ampIm /= ampScale
400  else:
401  mi /= scale
402 

◆ combine()

def lsst.cp.pipe.cpCombine.CalibCombineTask.combine (   self,
  target,
  expList,
  stats 
)
inherited
Combine multiple images.

Parameters
----------
target : `lsst.afw.image.Exposure`
    Output exposure to construct.
expList : `list` [`lsst.afw.image.Exposure`]
    Input exposures to combine.
stats : `lsst.afw.math.StatisticsControl`
    Control explaining how to combine the input images.

Definition at line 403 of file cpCombine.py.

403  def combine(self, target, expList, stats):
404  """Combine multiple images.
405 
406  Parameters
407  ----------
408  target : `lsst.afw.image.Exposure`
409  Output exposure to construct.
410  expList : `list` [`lsst.afw.image.Exposure`]
411  Input exposures to combine.
412  stats : `lsst.afw.math.StatisticsControl`
413  Control explaining how to combine the input images.
414  """
415  images = [img.getMaskedImage() for img in expList if img is not None]
416  combineType = afwMath.stringToStatisticsProperty(self.config.combine)
417  afwMath.statisticsStack(target, images, combineType, stats)
418 
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)
Definition: Statistics.cc:747
std::shared_ptr< lsst::afw::image::Image< PixelT > > statisticsStack(std::vector< std::shared_ptr< lsst::afw::image::Image< PixelT >>> &images, Property flags, StatisticsControl const &sctrl=StatisticsControl(), std::vector< lsst::afw::image::VariancePixel > const &wvector=std::vector< lsst::afw::image::VariancePixel >(0))
A function to compute some statistics of a stack of Images.

◆ combineHeaders()

def lsst.cp.pipe.cpCombine.CalibCombineTask.combineHeaders (   self,
  expList,
  calib,
  calibType = "CALIB",
  scales = None 
)
inherited
Combine input headers to determine the set of common headers,
supplemented by calibration inputs.

Parameters
----------
expList : `list` of `lsst.afw.image.Exposure`
    Input list of exposures to combine.
calib : `lsst.afw.image.Exposure`
    Output calibration to construct headers for.
calibType: `str`, optional
    OBSTYPE the output should claim.
scales: `list` of `float`, optional
    Scale values applied to each input to record.

Returns
-------
header : `lsst.daf.base.PropertyList`
    Constructed header.

Definition at line 419 of file cpCombine.py.

419  def combineHeaders(self, expList, calib, calibType="CALIB", scales=None):
420  """Combine input headers to determine the set of common headers,
421  supplemented by calibration inputs.
422 
423  Parameters
424  ----------
425  expList : `list` of `lsst.afw.image.Exposure`
426  Input list of exposures to combine.
427  calib : `lsst.afw.image.Exposure`
428  Output calibration to construct headers for.
429  calibType: `str`, optional
430  OBSTYPE the output should claim.
431  scales: `list` of `float`, optional
432  Scale values applied to each input to record.
433 
434  Returns
435  -------
436  header : `lsst.daf.base.PropertyList`
437  Constructed header.
438  """
439  # Header
440  header = calib.getMetadata()
441  header.set("OBSTYPE", calibType)
442 
443  # Keywords we care about
444  comments = {"TIMESYS": "Time scale for all dates",
445  "DATE-OBS": "Start date of earliest input observation",
446  "MJD-OBS": "[d] Start MJD of earliest input observation",
447  "DATE-END": "End date of oldest input observation",
448  "MJD-END": "[d] End MJD of oldest input observation",
449  "MJD-AVG": "[d] MJD midpoint of all input observations",
450  "DATE-AVG": "Midpoint date of all input observations"}
451 
452  # Creation date
453  now = time.localtime()
454  calibDate = time.strftime("%Y-%m-%d", now)
455  calibTime = time.strftime("%X %Z", now)
456  header.set("CALIB_CREATE_DATE", calibDate)
457  header.set("CALIB_CREATE_TIME", calibTime)
458 
459  # Merge input headers
460  inputHeaders = [exp.getMetadata() for exp in expList if exp is not None]
461  merged = merge_headers(inputHeaders, mode='drop')
462  for k, v in merged.items():
463  if k not in header:
464  md = expList[0].getMetadata()
465  comment = md.getComment(k) if k in md else None
466  header.set(k, v, comment=comment)
467 
468  # Construct list of visits
469  visitInfoList = [exp.getInfo().getVisitInfo() for exp in expList if exp is not None]
470  for i, visit in enumerate(visitInfoList):
471  if visit is None:
472  continue
473  header.set("CPP_INPUT_%d" % (i,), visit.getExposureId())
474  header.set("CPP_INPUT_DATE_%d" % (i,), str(visit.getDate()))
475  header.set("CPP_INPUT_EXPT_%d" % (i,), visit.getExposureTime())
476  if scales is not None:
477  header.set("CPP_INPUT_SCALE_%d" % (i,), scales[i])
478 
479  # Not yet working: DM-22302
480  # Create an observation group so we can add some standard headers
481  # independent of the form in the input files.
482  # Use try block in case we are dealing with unexpected data headers
483  try:
484  group = ObservationGroup(visitInfoList, pedantic=False)
485  except Exception:
486  self.log.warn("Exception making an obs group for headers. Continuing.")
487  # Fall back to setting a DATE-OBS from the calibDate
488  dateCards = {"DATE-OBS": "{}T00:00:00.00".format(calibDate)}
489  comments["DATE-OBS"] = "Date of start of day of calibration midpoint"
490  else:
491  oldest, newest = group.extremes()
492  dateCards = dates_to_fits(oldest.datetime_begin, newest.datetime_end)
493 
494  for k, v in dateCards.items():
495  header.set(k, v, comment=comments.get(k, None))
496 
497  return header
498 
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174

◆ getDimensions()

def lsst.cp.pipe.cpCombine.CalibCombineTask.getDimensions (   self,
  expList 
)
inherited
Get dimensions of the inputs.

Parameters
----------
expList : `list` [`lsst.afw.image.Exposure`]
    Exps to check the sizes of.

Returns
-------
width, height : `int`
    Unique set of input dimensions.

Definition at line 342 of file cpCombine.py.

342  def getDimensions(self, expList):
343  """Get dimensions of the inputs.
344 
345  Parameters
346  ----------
347  expList : `list` [`lsst.afw.image.Exposure`]
348  Exps to check the sizes of.
349 
350  Returns
351  -------
352  width, height : `int`
353  Unique set of input dimensions.
354  """
355  dimList = [exp.getDimensions() for exp in expList if exp is not None]
356  return self.getSize(dimList)
357 

◆ getSize()

def lsst.cp.pipe.cpCombine.CalibCombineTask.getSize (   self,
  dimList 
)
inherited
Determine a consistent size, given a list of image sizes.

Parameters
-----------
dimList : iterable of `tuple` (`int`, `int`)
    List of dimensions.

Raises
------
RuntimeError
    If input dimensions are inconsistent.

Returns
--------
width, height : `int`
    Common dimensions.

Definition at line 358 of file cpCombine.py.

358  def getSize(self, dimList):
359  """Determine a consistent size, given a list of image sizes.
360 
361  Parameters
362  -----------
363  dimList : iterable of `tuple` (`int`, `int`)
364  List of dimensions.
365 
366  Raises
367  ------
368  RuntimeError
369  If input dimensions are inconsistent.
370 
371  Returns
372  --------
373  width, height : `int`
374  Common dimensions.
375  """
376  dim = set((w, h) for w, h in dimList)
377  if len(dim) != 1:
378  raise RuntimeError("Inconsistent dimensions: %s" % dim)
379  return dim.pop()
380 
daf::base::PropertySet * set
Definition: fits.cc:912

◆ interpolateNans()

def lsst.cp.pipe.cpCombine.CalibCombineTask.interpolateNans (   self,
  exp 
)
inherited
Interpolate over NANs in the combined image.

NANs can result from masked areas on the CCD.  We don't want them getting
into our science images, so we replace them with the median of the image.

Parameters
----------
exp : `lsst.afw.image.Exposure`
    Exp to check for NaNs.

Definition at line 499 of file cpCombine.py.

499  def interpolateNans(self, exp):
500  """Interpolate over NANs in the combined image.
501 
502  NANs can result from masked areas on the CCD. We don't want them getting
503  into our science images, so we replace them with the median of the image.
504 
505  Parameters
506  ----------
507  exp : `lsst.afw.image.Exposure`
508  Exp to check for NaNs.
509  """
510  array = exp.getImage().getArray()
511  bad = np.isnan(array)
512 
513  median = np.median(array[np.logical_not(bad)])
514  count = np.sum(np.logical_not(bad))
515  array[bad] = median
516  if count > 0:
517  self.log.warn("Found %s NAN pixels", count)
518 
519 
520 # Create versions of the Connections, Config, and Task that support filter constraints.

◆ run()

def lsst.cp.pipe.cpCombine.CalibCombineTask.run (   self,
  inputExps,
  inputScales = None,
  inputDims = None 
)
inherited
Combine calib exposures for a single detector.

Parameters
----------
inputExps : `list` [`lsst.afw.image.Exposure`]
    Input list of exposures to combine.
inputScales : `dict` [`dict` [`dict` [`float`]]], optional
    Dictionary of scales, indexed by detector (`int`),
    amplifier (`int`), and exposure (`int`).  Used for
    'inputList' scaling.
inputDims : `list` [`dict`]
    List of dictionaries of input data dimensions/values.
    Each list entry should contain:

    ``"exposure"``
        exposure id value (`int`)
    ``"detector"``
        detector id value (`int`)

Returns
-------
combinedExp : `lsst.afw.image.Exposure`
    Final combined exposure generated from the inputs.

Raises
------
RuntimeError
    Raised if no input data is found.  Also raised if
    config.exposureScaling == InputList, and a necessary scale
    was not found.

Definition at line 216 of file cpCombine.py.

216  def run(self, inputExps, inputScales=None, inputDims=None):
217  """Combine calib exposures for a single detector.
218 
219  Parameters
220  ----------
221  inputExps : `list` [`lsst.afw.image.Exposure`]
222  Input list of exposures to combine.
223  inputScales : `dict` [`dict` [`dict` [`float`]]], optional
224  Dictionary of scales, indexed by detector (`int`),
225  amplifier (`int`), and exposure (`int`). Used for
226  'inputList' scaling.
227  inputDims : `list` [`dict`]
228  List of dictionaries of input data dimensions/values.
229  Each list entry should contain:
230 
231  ``"exposure"``
232  exposure id value (`int`)
233  ``"detector"``
234  detector id value (`int`)
235 
236  Returns
237  -------
238  combinedExp : `lsst.afw.image.Exposure`
239  Final combined exposure generated from the inputs.
240 
241  Raises
242  ------
243  RuntimeError
244  Raised if no input data is found. Also raised if
245  config.exposureScaling == InputList, and a necessary scale
246  was not found.
247  """
248  width, height = self.getDimensions(inputExps)
249  stats = afwMath.StatisticsControl(self.config.clip, self.config.nIter,
250  afwImage.Mask.getPlaneBitMask(self.config.mask))
251  numExps = len(inputExps)
252  if numExps < 1:
253  raise RuntimeError("No valid input data")
254  if numExps < self.config.maxVisitsToCalcErrorFromInputVariance:
255  stats.setCalcErrorFromInputVariance(True)
256 
257  # Check that all inputs either share the same detector (based
258  # on detId), or that no inputs have any detector.
259  detectorList = [exp.getDetector() for exp in inputExps]
260  if None in detectorList:
261  self.log.warn("Not all input detectors defined.")
262  detectorIds = [det.getId() if det is not None else None for det in detectorList]
263  detectorSerials = [det.getId() if det is not None else None for det in detectorList]
264  numDetectorIds = len(set(detectorIds))
265  numDetectorSerials = len(set(detectorSerials))
266  numDetectors = len(set([numDetectorIds, numDetectorSerials]))
267  if numDetectors != 1:
268  raise RuntimeError("Input data contains multiple detectors.")
269 
270  inputDetector = inputExps[0].getDetector()
271 
272  # Create output exposure for combined data.
273  combined = afwImage.MaskedImageF(width, height)
274  combinedExp = afwImage.makeExposure(combined)
275 
276  # Apply scaling:
277  expScales = []
278  if inputDims is None:
279  inputDims = [dict() for i in inputExps]
280 
281  for index, (exp, dims) in enumerate(zip(inputExps, inputDims)):
282  scale = 1.0
283  if exp is None:
284  self.log.warn("Input %d is None (%s); unable to scale exp.", index, dims)
285  continue
286 
287  if self.config.exposureScaling == "ExposureTime":
288  scale = exp.getInfo().getVisitInfo().getExposureTime()
289  elif self.config.exposureScaling == "DarkTime":
290  scale = exp.getInfo().getVisitInfo().getDarkTime()
291  elif self.config.exposureScaling == "MeanStats":
292  scale = self.stats.run(exp)
293  elif self.config.exposureScaling == "InputList":
294  visitId = dims.get('exposure', None)
295  detectorId = dims.get('detector', None)
296  if visitId is None or detectorId is None:
297  raise RuntimeError(f"Could not identify scaling for input {index} ({dims})")
298  if detectorId not in inputScales['expScale']:
299  raise RuntimeError(f"Could not identify a scaling for input {index}"
300  f" detector {detectorId}")
301 
302  if self.config.scalingLevel == "DETECTOR":
303  if visitId not in inputScales['expScale'][detectorId]:
304  raise RuntimeError(f"Could not identify a scaling for input {index}"
305  f"detector {detectorId} visit {visitId}")
306  scale = inputScales['expScale'][detectorId][visitId]
307  elif self.config.scalingLevel == 'AMP':
308  scale = [inputScales['expScale'][detectorId][amp.getName()][visitId]
309  for amp in exp.getDetector()]
310  else:
311  raise RuntimeError(f"Unknown scaling level: {self.config.scalingLevel}")
312  elif self.config.exposureScaling == 'Unity':
313  scale = 1.0
314  else:
315  raise RuntimeError(f"Unknown scaling type: {self.config.exposureScaling}.")
316 
317  expScales.append(scale)
318  self.log.info("Scaling input %d by %s", index, scale)
319  self.applyScale(exp, scale)
320 
321  self.combine(combined, inputExps, stats)
322 
323  self.interpolateNans(combined)
324 
325  if self.config.doVignette:
326  polygon = inputExps[0].getInfo().getValidPolygon()
327  VignetteExposure(combined, polygon=polygon, doUpdateMask=True,
328  doSetValue=True, vignetteValue=0.0)
329 
330  # Combine headers
331  self.combineHeaders(inputExps, combinedExp,
332  calibType=self.config.calibrationType, scales=expScales)
333 
334  # Set the detector
335  combinedExp.setDetector(inputDetector)
336 
337  # Return
338  return pipeBase.Struct(
339  outputData=combinedExp,
340  )
341 
Pass parameters to a Statistics object.
Definition: Statistics.h:93
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:462
def VignetteExposure(exposure, polygon=None, doUpdateMask=True, maskPlane="NO_DATA", doSetValue=False, vignetteValue=0.0, log=None)
Definition: cpCombine.py:561
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)

◆ runQuantum()

def lsst.cp.pipe.cpCombine.CalibCombineTask.runQuantum (   self,
  butlerQC,
  inputRefs,
  outputRefs 
)
inherited

Definition at line 207 of file cpCombine.py.

207  def runQuantum(self, butlerQC, inputRefs, outputRefs):
208  inputs = butlerQC.get(inputRefs)
209 
210  dimensions = [exp.dataId.byName() for exp in inputRefs.inputExps]
211  inputs['inputDims'] = dimensions
212 
213  outputs = self.run(**inputs)
214  butlerQC.put(outputs, outputRefs)
215 

Member Data Documentation

◆ ConfigClass

lsst.cp.pipe.cpCombine.CalibCombineByFilterTask.ConfigClass = CalibCombineByFilterConfig
static

Definition at line 553 of file cpCombine.py.


The documentation for this class was generated from the following file: