LSSTApplications  18.0.0+66,19.0.0+42,19.0.0+45,19.0.0+49,19.0.0+5,19.0.0+54,19.0.0+9,19.0.0-1-g20d9b18+22,19.0.0-1-g49a97f9+3,19.0.0-1-g5549ca4+4,19.0.0-1-g8c57eb9+22,19.0.0-1-ga72da6b+3,19.0.0-1-gbfe0924+36,19.0.0-1-ge272bc4+22,19.0.0-1-gefe1d0d+24,19.0.0-10-g5d04f37+1,19.0.0-11-g10f13ba+3,19.0.0-13-gafd90fe+1,19.0.0-14-g5673ca6+1,19.0.0-14-g706b86db4,19.0.0-2-g0d9f9cd+46,19.0.0-2-g260436e+28,19.0.0-2-g9675b69+3,19.0.0-2-g9b11441+34,19.0.0-2-gde8e5e3+3,19.0.0-2-gf01c5b1+1,19.0.0-2-gff6972b+6,19.0.0-26-g830ab5e+1,19.0.0-3-g27e4659+13,19.0.0-3-g6513920+38,19.0.0-3-gce3f959+23,19.0.0-33-ge0e6817e,19.0.0-5-g9aa49c1+2,19.0.0-7-g686a884+4,19.0.0-7-ga57c4689+21,19.0.0-8-g079e426+11,w.2020.11
LSSTDataManagementBasePackage
Public Member Functions | Static Public Member Functions | Public Attributes | Static Public Attributes | List of all members
lsst.pipe.tasks.imageDifference.ImageDifferenceTask Class Reference
Inheritance diagram for lsst.pipe.tasks.imageDifference.ImageDifferenceTask:
lsst.pipe.tasks.imageDifference.Winter2013ImageDifferenceTask

Public Member Functions

def __init__ (self, butler=None, kwargs)
 Construct an ImageDifference Task. More...
 
def runDataRef (self, sensorRef, templateIdList=None)
 
def run (self, exposure=None, selectSources=None, templateExposure=None, templateSources=None, idFactory=None, calexpBackgroundExposure=None, subtractedExposure=None)
 
def fitAstrometry (self, templateSources, templateExposure, selectSources)
 
def runDebug (self, exposure, subtractRes, selectSources, kernelSources, diaSources)
 
def getSchemaCatalogs (self)
 

Static Public Member Functions

def makeIdFactory (expId, expBits)
 

Public Attributes

 schema
 
 algMetadata
 

Static Public Attributes

 ConfigClass
 
 RunnerClass
 

Detailed Description

Subtract an image from a template and measure the result

Definition at line 253 of file imageDifference.py.

Constructor & Destructor Documentation

◆ __init__()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.__init__ (   self,
  butler = None,
  kwargs 
)

Construct an ImageDifference Task.

Parameters
[in]butlerButler object to use in constructing reference object loaders

Definition at line 260 of file imageDifference.py.

260  def __init__(self, butler=None, **kwargs):
261  """!Construct an ImageDifference Task
262 
263  @param[in] butler Butler object to use in constructing reference object loaders
264  """
265  pipeBase.CmdLineTask.__init__(self, **kwargs)
266  self.makeSubtask("getTemplate")
267 
268  self.makeSubtask("subtract")
269 
270  if self.config.subtract.name == 'al' and self.config.doDecorrelation:
271  self.makeSubtask("decorrelate")
272 
273  if self.config.doScaleTemplateVariance:
274  self.makeSubtask("scaleVariance")
275 
276  if self.config.doUseRegister:
277  self.makeSubtask("register")
278  self.schema = afwTable.SourceTable.makeMinimalSchema()
279 
280  if self.config.doSelectSources:
281  self.makeSubtask("sourceSelector")
282  if self.config.kernelSourcesFromRef:
283  self.makeSubtask('refObjLoader', butler=butler)
284  self.makeSubtask("astrometer", refObjLoader=self.refObjLoader)
285 
286  self.algMetadata = dafBase.PropertyList()
287  if self.config.doDetection:
288  self.makeSubtask("detection", schema=self.schema)
289  if self.config.doMeasurement:
290  self.makeSubtask("measurement", schema=self.schema,
291  algMetadata=self.algMetadata)
292  if self.config.doForcedMeasurement:
293  self.schema.addField(
294  "ip_diffim_forced_PsfFlux_instFlux", "D",
295  "Forced PSF flux measured on the direct image.")
296  self.schema.addField(
297  "ip_diffim_forced_PsfFlux_instFluxErr", "D",
298  "Forced PSF flux error measured on the direct image.")
299  self.schema.addField(
300  "ip_diffim_forced_PsfFlux_area", "F",
301  "Forced PSF flux effective area of PSF.",
302  units="pixel")
303  self.schema.addField(
304  "ip_diffim_forced_PsfFlux_flag", "Flag",
305  "Forced PSF flux general failure flag.")
306  self.schema.addField(
307  "ip_diffim_forced_PsfFlux_flag_noGoodPixels", "Flag",
308  "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
309  self.schema.addField(
310  "ip_diffim_forced_PsfFlux_flag_edge", "Flag",
311  "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
312  self.makeSubtask("forcedMeasurement", refSchema=self.schema)
313  if self.config.doMatchSources:
314  self.schema.addField("refMatchId", "L", "unique id of reference catalog match")
315  self.schema.addField("srcMatchId", "L", "unique id of source match")
316 
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68

Member Function Documentation

◆ fitAstrometry()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.fitAstrometry (   self,
  templateSources,
  templateExposure,
  selectSources 
)
Fit the relative astrometry between templateSources and selectSources

Todo
----

Remove this method. It originally fit a new WCS to the template before calling register.run
because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed.
It remains because a subtask overrides it.

Definition at line 885 of file imageDifference.py.

885  def fitAstrometry(self, templateSources, templateExposure, selectSources):
886  """Fit the relative astrometry between templateSources and selectSources
887 
888  Todo
889  ----
890 
891  Remove this method. It originally fit a new WCS to the template before calling register.run
892  because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed.
893  It remains because a subtask overrides it.
894  """
895  results = self.register.run(templateSources, templateExposure.getWcs(),
896  templateExposure.getBBox(), selectSources)
897  return results
898 
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)

◆ getSchemaCatalogs()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.getSchemaCatalogs (   self)
Return a dict of empty catalogs for each catalog dataset produced by this task.

Definition at line 987 of file imageDifference.py.

987  def getSchemaCatalogs(self):
988  """Return a dict of empty catalogs for each catalog dataset produced by this task."""
989  diaSrc = afwTable.SourceCatalog(self.schema)
990  diaSrc.getTable().setMetadata(self.algMetadata)
991  return {self.config.coaddName + "Diff_diaSrc": diaSrc}
992 

◆ makeIdFactory()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.makeIdFactory (   expId,
  expBits 
)
static
Create IdFactory instance for unique 64 bit diaSource id-s.

Parameters
----------
expId : `int`
    Exposure id.

expBits: `int`
    Number of used bits in ``expId``.

Note
----
The diasource id-s consists of the ``expId`` stored fixed in the highest value
``expBits`` of the 64-bit integer plus (bitwise or) a generated sequence number in the
low value end of the integer.

Returns
-------
idFactory: `lsst.afw.table.IdFactory`

Definition at line 318 of file imageDifference.py.

318  def makeIdFactory(expId, expBits):
319  """Create IdFactory instance for unique 64 bit diaSource id-s.
320 
321  Parameters
322  ----------
323  expId : `int`
324  Exposure id.
325 
326  expBits: `int`
327  Number of used bits in ``expId``.
328 
329  Note
330  ----
331  The diasource id-s consists of the ``expId`` stored fixed in the highest value
332  ``expBits`` of the 64-bit integer plus (bitwise or) a generated sequence number in the
333  low value end of the integer.
334 
335  Returns
336  -------
337  idFactory: `lsst.afw.table.IdFactory`
338  """
339  return afwTable.IdFactory.makeSource(expId, 64 - expBits)
340 

◆ run()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.run (   self,
  exposure = None,
  selectSources = None,
  templateExposure = None,
  templateSources = None,
  idFactory = None,
  calexpBackgroundExposure = None,
  subtractedExposure = None 
)
PSF matches, subtract two images and perform detection on the difference image.

Parameters
----------
exposure : `lsst.afw.image.ExposureF`, optional
    The science exposure, the minuend in the image subtraction.
    Can be None only if ``config.doSubtract==False``.
selectSources : `lsst.afw.table.SourceCatalog`, optional
    Identified sources on the science exposure. This catalog is used to
    select sources in order to perform the AL PSF matching on stamp images
    around them. The selection steps depend on config options and whether
    ``templateSources`` and ``matchingSources`` specified.
templateExposure : `lsst.afw.image.ExposureF`, optional
    The template to be subtracted from ``exposure`` in the image subtraction.
    The template exposure should cover the same sky area as the science exposure.
    It is either a stich of patches of a coadd skymap image or a calexp
    of the same pointing as the science exposure. Can be None only
    if ``config.doSubtract==False`` and ``subtractedExposure`` is not None.
templateSources : `lsst.afw.table.SourceCatalog`, optional
    Identified sources on the template exposure.
idFactory : `lsst.afw.table.IdFactory`
    Generator object to assign ids to detected sources in the difference image.
calexpBackgroundExposure : `lsst.afw.image.ExposureF`, optional
    Background exposure to be added back to the science exposure
    if ``config.doAddCalexpBackground==True``
subtractedExposure : `lsst.afw.image.ExposureF`, optional
    If ``config.doSubtract==False`` and ``config.doDetection==True``,
    performs the post subtraction source detection only on this exposure.
    Otherwise should be None.

Returns
-------
results : `lsst.pipe.base.Struct`
    ``subtractedExposure`` : `lsst.afw.image.ExposureF`
Difference image.
    ``matchedExposure`` : `lsst.afw.image.ExposureF`
The matched PSF exposure.
    ``subtractRes`` : `lsst.pipe.base.Struct`
The returned result structure of the ImagePsfMatchTask subtask.
    ``diaSources``  : `lsst.afw.table.SourceCatalog`
The catalog of detected sources.
    ``selectSources`` : `lsst.afw.table.SourceCatalog`
The input source catalog with optionally added Qa information.

Notes
-----
The following major steps are included:

- warp template coadd to match WCS of image
- PSF match image to warped template
- subtract image from PSF-matched, warped template
- detect sources
- measure sources

For details about the image subtraction configuration modes
see `lsst.ip.diffim`.

Definition at line 419 of file imageDifference.py.

419  idFactory=None, calexpBackgroundExposure=None, subtractedExposure=None):
420  """PSF matches, subtract two images and perform detection on the difference image.
421 
422  Parameters
423  ----------
424  exposure : `lsst.afw.image.ExposureF`, optional
425  The science exposure, the minuend in the image subtraction.
426  Can be None only if ``config.doSubtract==False``.
427  selectSources : `lsst.afw.table.SourceCatalog`, optional
428  Identified sources on the science exposure. This catalog is used to
429  select sources in order to perform the AL PSF matching on stamp images
430  around them. The selection steps depend on config options and whether
431  ``templateSources`` and ``matchingSources`` specified.
432  templateExposure : `lsst.afw.image.ExposureF`, optional
433  The template to be subtracted from ``exposure`` in the image subtraction.
434  The template exposure should cover the same sky area as the science exposure.
435  It is either a stich of patches of a coadd skymap image or a calexp
436  of the same pointing as the science exposure. Can be None only
437  if ``config.doSubtract==False`` and ``subtractedExposure`` is not None.
438  templateSources : `lsst.afw.table.SourceCatalog`, optional
439  Identified sources on the template exposure.
440  idFactory : `lsst.afw.table.IdFactory`
441  Generator object to assign ids to detected sources in the difference image.
442  calexpBackgroundExposure : `lsst.afw.image.ExposureF`, optional
443  Background exposure to be added back to the science exposure
444  if ``config.doAddCalexpBackground==True``
445  subtractedExposure : `lsst.afw.image.ExposureF`, optional
446  If ``config.doSubtract==False`` and ``config.doDetection==True``,
447  performs the post subtraction source detection only on this exposure.
448  Otherwise should be None.
449 
450  Returns
451  -------
452  results : `lsst.pipe.base.Struct`
453  ``subtractedExposure`` : `lsst.afw.image.ExposureF`
454  Difference image.
455  ``matchedExposure`` : `lsst.afw.image.ExposureF`
456  The matched PSF exposure.
457  ``subtractRes`` : `lsst.pipe.base.Struct`
458  The returned result structure of the ImagePsfMatchTask subtask.
459  ``diaSources`` : `lsst.afw.table.SourceCatalog`
460  The catalog of detected sources.
461  ``selectSources`` : `lsst.afw.table.SourceCatalog`
462  The input source catalog with optionally added Qa information.
463 
464  Notes
465  -----
466  The following major steps are included:
467 
468  - warp template coadd to match WCS of image
469  - PSF match image to warped template
470  - subtract image from PSF-matched, warped template
471  - detect sources
472  - measure sources
473 
474  For details about the image subtraction configuration modes
475  see `lsst.ip.diffim`.
476  """
477  subtractRes = None
478  controlSources = None
479  diaSources = None
480  kernelSources = None
481 
482  if self.config.doAddCalexpBackground:
483  mi = exposure.getMaskedImage()
484  mi += calexpBackgroundExposure.getImage()
485 
486  if not exposure.hasPsf():
487  raise pipeBase.TaskError("Exposure has no psf")
488  sciencePsf = exposure.getPsf()
489 
490  if self.config.doSubtract:
491  if self.config.doScaleTemplateVariance:
492  templateVarFactor = self.scaleVariance.run(
493  templateExposure.getMaskedImage())
494  self.metadata.add("scaleTemplateVarianceFactor", templateVarFactor)
495 
496  if self.config.subtract.name == 'zogy':
497  subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
498  doWarping=True,
499  spatiallyVarying=self.config.doSpatiallyVarying,
500  doPreConvolve=self.config.doPreConvolve)
501  subtractedExposure = subtractRes.subtractedExposure
502 
503  elif self.config.subtract.name == 'al':
504  # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
505  scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
506  templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
507 
508  # if requested, convolve the science exposure with its PSF
509  # (properly, this should be a cross-correlation, but our code does not yet support that)
510  # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
511  # else sigma of original science exposure
512  # TODO: DM-22762 This functional block should be moved into its own method
513  preConvPsf = None
514  if self.config.doPreConvolve:
515  convControl = afwMath.ConvolutionControl()
516  # cannot convolve in place, so make a new MI to receive convolved image
517  srcMI = exposure.getMaskedImage()
518  destMI = srcMI.Factory(srcMI.getDimensions())
519  srcPsf = sciencePsf
520  if self.config.useGaussianForPreConvolution:
521  # convolve with a simplified PSF model: a double Gaussian
522  kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
523  preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
524  else:
525  # convolve with science exposure's PSF model
526  preConvPsf = srcPsf
527  afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
528  exposure.setMaskedImage(destMI)
529  scienceSigmaPost = scienceSigmaOrig*math.sqrt(2)
530  else:
531  scienceSigmaPost = scienceSigmaOrig
532 
533  # If requested, find and select sources from the image
534  # else, AL subtraction will do its own source detection
535  # TODO: DM-22762 This functional block should be moved into its own method
536  if self.config.doSelectSources:
537  if selectSources is None:
538  self.log.warn("Src product does not exist; running detection, measurement, selection")
539  # Run own detection and measurement; necessary in nightly processing
540  selectSources = self.subtract.getSelectSources(
541  exposure,
542  sigma=scienceSigmaPost,
543  doSmooth=not self.doPreConvolve,
544  idFactory=idFactory,
545  )
546 
547  if self.config.doAddMetrics:
548  # Number of basis functions
549 
550  nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
551  referenceFwhmPix=scienceSigmaPost*FwhmPerSigma,
552  targetFwhmPix=templateSigma*FwhmPerSigma))
553  # Modify the schema of all Sources
554  # DEPRECATED: This is a data dependent (nparam) output product schema
555  # outside the task constructor.
556  # NOTE: The pre-determination of nparam at this point
557  # may be incorrect as the template psf is warped later in
558  # ImagePsfMatchTask.matchExposures()
559  kcQa = KernelCandidateQa(nparam)
560  selectSources = kcQa.addToSchema(selectSources)
561  if self.config.kernelSourcesFromRef:
562  # match exposure sources to reference catalog
563  astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
564  matches = astromRet.matches
565  elif templateSources:
566  # match exposure sources to template sources
567  mc = afwTable.MatchControl()
568  mc.findOnlyClosest = False
569  matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*geom.arcseconds,
570  mc)
571  else:
572  raise RuntimeError("doSelectSources=True and kernelSourcesFromRef=False,"
573  "but template sources not available. Cannot match science "
574  "sources with template sources. Run process* on data from "
575  "which templates are built.")
576 
577  kernelSources = self.sourceSelector.run(selectSources, exposure=exposure,
578  matches=matches).sourceCat
579  random.shuffle(kernelSources, random.random)
580  controlSources = kernelSources[::self.config.controlStepSize]
581  kernelSources = [k for i, k in enumerate(kernelSources)
582  if i % self.config.controlStepSize]
583 
584  if self.config.doSelectDcrCatalog:
585  redSelector = DiaCatalogSourceSelectorTask(
586  DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
587  grMax=99.999))
588  redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
589  controlSources.extend(redSources)
590 
591  blueSelector = DiaCatalogSourceSelectorTask(
592  DiaCatalogSourceSelectorConfig(grMin=-99.999,
593  grMax=self.sourceSelector.config.grMin))
594  blueSources = blueSelector.selectStars(exposure, selectSources,
595  matches=matches).starCat
596  controlSources.extend(blueSources)
597 
598  if self.config.doSelectVariableCatalog:
599  varSelector = DiaCatalogSourceSelectorTask(
600  DiaCatalogSourceSelectorConfig(includeVariable=True))
601  varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
602  controlSources.extend(varSources)
603 
604  self.log.info("Selected %d / %d sources for Psf matching (%d for control sample)"
605  % (len(kernelSources), len(selectSources), len(controlSources)))
606 
607  allresids = {}
608  # TODO: DM-22762 This functional block should be moved into its own method
609  if self.config.doUseRegister:
610  self.log.info("Registering images")
611 
612  if templateSources is None:
613  # Run detection on the template, which is
614  # temporarily background-subtracted
615  # sigma of PSF of template image before warping
616  templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
617  templateSources = self.subtract.getSelectSources(
618  templateExposure,
619  sigma=templateSigma,
620  doSmooth=True,
621  idFactory=idFactory
622  )
623 
624  # Third step: we need to fit the relative astrometry.
625  #
626  wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources)
627  warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
628  exposure.getWcs(), exposure.getBBox())
629  templateExposure = warpedExp
630 
631  # Create debugging outputs on the astrometric
632  # residuals as a function of position. Persistence
633  # not yet implemented; expected on (I believe) #2636.
634  if self.config.doDebugRegister:
635  # Grab matches to reference catalog
636  srcToMatch = {x.second.getId(): x.first for x in matches}
637 
638  refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
639  inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
640  sids = [m.first.getId() for m in wcsResults.matches]
641  positions = [m.first.get(refCoordKey) for m in wcsResults.matches]
642  residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
643  m.second.get(inCentroidKey))) for m in wcsResults.matches]
644  allresids = dict(zip(sids, zip(positions, residuals)))
645 
646  cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
647  wcsResults.wcs.pixelToSky(
648  m.second.get(inCentroidKey))) for m in wcsResults.matches]
649  colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get("g")) +
650  2.5*numpy.log10(srcToMatch[x].get("r"))
651  for x in sids if x in srcToMatch.keys()])
652  dlong = numpy.array([r[0].asArcseconds() for s, r in zip(sids, cresiduals)
653  if s in srcToMatch.keys()])
654  dlat = numpy.array([r[1].asArcseconds() for s, r in zip(sids, cresiduals)
655  if s in srcToMatch.keys()])
656  idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
657  idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
658  (colors <= self.sourceSelector.config.grMax))
659  idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
660  rms1Long = IqrToSigma*(
661  (numpy.percentile(dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)))
662  rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
663  numpy.percentile(dlat[idx1], 25))
664  rms2Long = IqrToSigma*(
665  (numpy.percentile(dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)))
666  rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
667  numpy.percentile(dlat[idx2], 25))
668  rms3Long = IqrToSigma*(
669  (numpy.percentile(dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)))
670  rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
671  numpy.percentile(dlat[idx3], 25))
672  self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f" %
673  (numpy.median(dlong[idx1]), rms1Long,
674  numpy.median(dlat[idx1]), rms1Lat))
675  self.log.info("Green star offsets'': %.3f %.3f, %.3f %.3f" %
676  (numpy.median(dlong[idx2]), rms2Long,
677  numpy.median(dlat[idx2]), rms2Lat))
678  self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f" %
679  (numpy.median(dlong[idx3]), rms3Long,
680  numpy.median(dlat[idx3]), rms3Lat))
681 
682  self.metadata.add("RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
683  self.metadata.add("RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
684  self.metadata.add("RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
685  self.metadata.add("RegisterBlueLongOffsetStd", rms1Long)
686  self.metadata.add("RegisterGreenLongOffsetStd", rms2Long)
687  self.metadata.add("RegisterRedLongOffsetStd", rms3Long)
688 
689  self.metadata.add("RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
690  self.metadata.add("RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
691  self.metadata.add("RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
692  self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat)
693  self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat)
694  self.metadata.add("RegisterRedLatOffsetStd", rms3Lat)
695 
696  # warp template exposure to match exposure,
697  # PSF match template exposure to exposure,
698  # then return the difference
699 
700  # Return warped template... Construct sourceKernelCand list after subtract
701  self.log.info("Subtracting images")
702  subtractRes = self.subtract.subtractExposures(
703  templateExposure=templateExposure,
704  scienceExposure=exposure,
705  candidateList=kernelSources,
706  convolveTemplate=self.config.convolveTemplate,
707  doWarping=not self.config.doUseRegister
708  )
709  subtractedExposure = subtractRes.subtractedExposure
710 
711  if self.config.doDetection:
712  self.log.info("Computing diffim PSF")
713 
714  # Get Psf from the appropriate input image if it doesn't exist
715  if not subtractedExposure.hasPsf():
716  if self.config.convolveTemplate:
717  subtractedExposure.setPsf(exposure.getPsf())
718  else:
719  subtractedExposure.setPsf(templateExposure.getPsf())
720 
721  # If doSubtract is False, then subtractedExposure was fetched from disk (above),
722  # thus it may have already been decorrelated. Thus, we do not decorrelate if
723  # doSubtract is False.
724 
725  # NOTE: At this point doSubtract == True
726  if self.config.doDecorrelation and self.config.doSubtract:
727  preConvKernel = None
728  if preConvPsf is not None:
729  preConvKernel = preConvPsf.getLocalKernel()
730  if self.config.convolveTemplate:
731  self.log.info("Decorrelation after template image convolution")
732  decorrResult = self.decorrelate.run(exposure, templateExposure,
733  subtractedExposure,
734  subtractRes.psfMatchingKernel,
735  spatiallyVarying=self.config.doSpatiallyVarying,
736  preConvKernel=preConvKernel)
737  else:
738  self.log.info("Decorrelation after science image convolution")
739  decorrResult = self.decorrelate.run(templateExposure, exposure,
740  subtractedExposure,
741  subtractRes.psfMatchingKernel,
742  spatiallyVarying=self.config.doSpatiallyVarying,
743  preConvKernel=preConvKernel)
744  subtractedExposure = decorrResult.correctedExposure
745 
746  # END (if subtractAlgorithm == 'AL')
747  # END (if self.config.doSubtract)
748  if self.config.doDetection:
749  self.log.info("Running diaSource detection")
750  # Erase existing detection mask planes
751  mask = subtractedExposure.getMaskedImage().getMask()
752  mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))
753 
754  table = afwTable.SourceTable.make(self.schema, idFactory)
755  table.setMetadata(self.algMetadata)
756  results = self.detection.run(
757  table=table,
758  exposure=subtractedExposure,
759  doSmooth=not self.config.doPreConvolve
760  )
761 
762  if self.config.doMerge:
763  fpSet = results.fpSets.positive
764  fpSet.merge(results.fpSets.negative, self.config.growFootprint,
765  self.config.growFootprint, False)
766  diaSources = afwTable.SourceCatalog(table)
767  fpSet.makeSources(diaSources)
768  self.log.info("Merging detections into %d sources" % (len(diaSources)))
769  else:
770  diaSources = results.sources
771 
772  if self.config.doMeasurement:
773  newDipoleFitting = self.config.doDipoleFitting
774  self.log.info("Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
775  if not newDipoleFitting:
776  # Just fit dipole in diffim
777  self.measurement.run(diaSources, subtractedExposure)
778  else:
779  # Use (matched) template and science image (if avail.) to constrain dipole fitting
780  if self.config.doSubtract and 'matchedExposure' in subtractRes.getDict():
781  self.measurement.run(diaSources, subtractedExposure, exposure,
782  subtractRes.matchedExposure)
783  else:
784  self.measurement.run(diaSources, subtractedExposure, exposure)
785 
786  if self.config.doForcedMeasurement:
787  # Run forced psf photometry on the PVI at the diaSource locations.
788  # Copy the measured flux and error into the diaSource.
789  forcedSources = self.forcedMeasurement.generateMeasCat(
790  exposure, diaSources, subtractedExposure.getWcs())
791  self.forcedMeasurement.run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
792  mapper = afwTable.SchemaMapper(forcedSources.schema, diaSources.schema)
793  mapper.addMapping(forcedSources.schema.find("base_PsfFlux_instFlux")[0],
794  "ip_diffim_forced_PsfFlux_instFlux", True)
795  mapper.addMapping(forcedSources.schema.find("base_PsfFlux_instFluxErr")[0],
796  "ip_diffim_forced_PsfFlux_instFluxErr", True)
797  mapper.addMapping(forcedSources.schema.find("base_PsfFlux_area")[0],
798  "ip_diffim_forced_PsfFlux_area", True)
799  mapper.addMapping(forcedSources.schema.find("base_PsfFlux_flag")[0],
800  "ip_diffim_forced_PsfFlux_flag", True)
801  mapper.addMapping(forcedSources.schema.find("base_PsfFlux_flag_noGoodPixels")[0],
802  "ip_diffim_forced_PsfFlux_flag_noGoodPixels", True)
803  mapper.addMapping(forcedSources.schema.find("base_PsfFlux_flag_edge")[0],
804  "ip_diffim_forced_PsfFlux_flag_edge", True)
805  for diaSource, forcedSource in zip(diaSources, forcedSources):
806  diaSource.assign(forcedSource, mapper)
807 
808  # Match with the calexp sources if possible
809  if self.config.doMatchSources:
810  if selectSources is not None:
811  # Create key,val pair where key=diaSourceId and val=sourceId
812  matchRadAsec = self.config.diaSourceMatchRadius
813  matchRadPixel = matchRadAsec/exposure.getWcs().getPixelScale().asArcseconds()
814 
815  srcMatches = afwTable.matchXy(selectSources, diaSources, matchRadPixel)
816  srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) for
817  srcMatch in srcMatches])
818  self.log.info("Matched %d / %d diaSources to sources" % (len(srcMatchDict),
819  len(diaSources)))
820  else:
821  self.log.warn("Src product does not exist; cannot match with diaSources")
822  srcMatchDict = {}
823 
824  # Create key,val pair where key=diaSourceId and val=refId
825  refAstromConfig = AstrometryConfig()
826  refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
827  refAstrometer = AstrometryTask(refAstromConfig)
828  astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
829  refMatches = astromRet.matches
830  if refMatches is None:
831  self.log.warn("No diaSource matches with reference catalog")
832  refMatchDict = {}
833  else:
834  self.log.info("Matched %d / %d diaSources to reference catalog" % (len(refMatches),
835  len(diaSources)))
836  refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for
837  refMatch in refMatches])
838 
839  # Assign source Ids
840  for diaSource in diaSources:
841  sid = diaSource.getId()
842  if sid in srcMatchDict:
843  diaSource.set("srcMatchId", srcMatchDict[sid])
844  if sid in refMatchDict:
845  diaSource.set("refMatchId", refMatchDict[sid])
846 
847  if self.config.doAddMetrics and self.config.doSelectSources:
848  self.log.info("Evaluating metrics and control sample")
849 
850  kernelCandList = []
851  for cell in subtractRes.kernelCellSet.getCellList():
852  for cand in cell.begin(False): # include bad candidates
853  kernelCandList.append(cand)
854 
855  # Get basis list to build control sample kernels
856  basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
857  nparam = len(kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelParameters())
858 
859  controlCandList = (
860  diffimTools.sourceTableToCandidateList(controlSources,
861  subtractRes.warpedExposure, exposure,
862  self.config.subtract.kernel.active,
863  self.config.subtract.kernel.active.detectionConfig,
864  self.log, doBuild=True, basisList=basisList))
865 
866  KernelCandidateQa.apply(kernelCandList, subtractRes.psfMatchingKernel,
867  subtractRes.backgroundModel, dof=nparam)
868  KernelCandidateQa.apply(controlCandList, subtractRes.psfMatchingKernel,
869  subtractRes.backgroundModel)
870 
871  if self.config.doDetection:
872  KernelCandidateQa.aggregate(selectSources, self.metadata, allresids, diaSources)
873  else:
874  KernelCandidateQa.aggregate(selectSources, self.metadata, allresids)
875 
876  self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
877  return pipeBase.Struct(
878  subtractedExposure=subtractedExposure,
879  matchedExposure=subtractRes.matchedExposure,
880  subtractRes=subtractRes,
881  diaSources=diaSources,
882  selectSources=selectSources
883  )
884 
def makeKernelBasisList(config, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, metadata=None)
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:21
Parameters to control convolution.
Definition: ConvolveImage.h:50
template SourceMatchVector matchRaDec(SourceCatalog const &, lsst::geom::Angle, MatchControl const &)
Pass parameters to algorithms that match list of sources.
Definition: Match.h:45
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
SourceMatchVector matchXy(SourceCatalog const &cat, double radius, bool symmetric)
Compute all tuples (s1,s2,d) where s1 != s2, s1 and s2 both belong to cat, and d, the distance betwee...
Definition: Match.cc:383

◆ runDataRef()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.runDataRef (   self,
  sensorRef,
  templateIdList = None 
)
Subtract an image from a template coadd and measure the result.

Data I/O wrapper around `run` using the butler in Gen2.

Parameters
----------
sensorRef : `lsst.daf.persistence.ButlerDataRef`
    Sensor-level butler data reference, used for the following data products:

    Input only:
    - calexp
    - psf
    - ccdExposureId
    - ccdExposureId_bits
    - self.config.coaddName + "Coadd_skyMap"
    - self.config.coaddName + "Coadd"
    Input or output, depending on config:
    - self.config.coaddName + "Diff_subtractedExp"
    Output, depending on config:
    - self.config.coaddName + "Diff_matchedExp"
    - self.config.coaddName + "Diff_src"

Returns
-------
results : `lsst.pipe.base.Struct`
    Returns the Struct by `run`.

Definition at line 342 of file imageDifference.py.

342  def runDataRef(self, sensorRef, templateIdList=None):
343  """Subtract an image from a template coadd and measure the result.
344 
345  Data I/O wrapper around `run` using the butler in Gen2.
346 
347  Parameters
348  ----------
349  sensorRef : `lsst.daf.persistence.ButlerDataRef`
350  Sensor-level butler data reference, used for the following data products:
351 
352  Input only:
353  - calexp
354  - psf
355  - ccdExposureId
356  - ccdExposureId_bits
357  - self.config.coaddName + "Coadd_skyMap"
358  - self.config.coaddName + "Coadd"
359  Input or output, depending on config:
360  - self.config.coaddName + "Diff_subtractedExp"
361  Output, depending on config:
362  - self.config.coaddName + "Diff_matchedExp"
363  - self.config.coaddName + "Diff_src"
364 
365  Returns
366  -------
367  results : `lsst.pipe.base.Struct`
368  Returns the Struct by `run`.
369  """
370  subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
371  subtractedExposure = None
372  selectSources = None
373  calexpBackgroundExposure = None
374  self.log.info("Processing %s" % (sensorRef.dataId))
375 
376  # We make one IdFactory that will be used by both icSrc and src datasets;
377  # I don't know if this is the way we ultimately want to do things, but at least
378  # this ensures the source IDs are fully unique.
379  idFactory = self.makeIdFactory(expId=int(sensorRef.get("ccdExposureId")),
380  expBits=sensorRef.get("ccdExposureId_bits"))
381  if self.config.doAddCalexpBackground:
382  calexpBackgroundExposure = sensorRef.get("calexpBackground")
383 
384  # Retrieve the science image we wish to analyze
385  exposure = sensorRef.get("calexp", immediate=True)
386 
387  # Retrieve the template image
388  template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
389 
390  if sensorRef.datasetExists("src"):
391  self.log.info("Source selection via src product")
392  # Sources already exist; for data release processing
393  selectSources = sensorRef.get("src")
394 
395  if not self.config.doSubtract and self.config.doDetection:
396  # If we don't do subtraction, we need the subtracted exposure from the repo
397  subtractedExposure = sensorRef.get(subtractedExposureName)
398  # Both doSubtract and doDetection cannot be False
399 
400  results = self.run(exposure=exposure,
401  selectSources=selectSources,
402  templateExposure=template.exposure,
403  templateSources=template.sources,
404  idFactory=idFactory,
405  calexpBackgroundExposure=calexpBackgroundExposure,
406  subtractedExposure=subtractedExposure)
407 
408  if self.config.doWriteSources and results.diaSources is not None:
409  sensorRef.put(results.diaSources, self.config.coaddName + "Diff_diaSrc")
410  if self.config.doWriteMatchedExp:
411  sensorRef.put(results.matchedExposure, self.config.coaddName + "Diff_matchedExp")
412  if self.config.doAddMetrics and self.config.doSelectSources:
413  sensorRef.put(results.selectSources, self.config.coaddName + "Diff_kernelSrc")
414  if self.config.doWriteSubtractedExp:
415  sensorRef.put(results.subtractedExposure, subtractedExposureName)
416  return results
417 
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)

◆ runDebug()

def lsst.pipe.tasks.imageDifference.ImageDifferenceTask.runDebug (   self,
  exposure,
  subtractRes,
  selectSources,
  kernelSources,
  diaSources 
)
Make debug plots and displays.

Todo
----
Test and update for current debug display and slot names

Definition at line 899 of file imageDifference.py.

899  def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
900  """Make debug plots and displays.
901 
902  Todo
903  ----
904  Test and update for current debug display and slot names
905  """
906  import lsstDebug
907  display = lsstDebug.Info(__name__).display
908  showSubtracted = lsstDebug.Info(__name__).showSubtracted
909  showPixelResiduals = lsstDebug.Info(__name__).showPixelResiduals
910  showDiaSources = lsstDebug.Info(__name__).showDiaSources
911  showDipoles = lsstDebug.Info(__name__).showDipoles
912  maskTransparency = lsstDebug.Info(__name__).maskTransparency
913  if display:
914  disp = afwDisplay.getDisplay(frame=lsstDebug.frame)
915  if not maskTransparency:
916  maskTransparency = 0
917  disp.setMaskTransparency(maskTransparency)
918 
919  if display and showSubtracted:
920  disp.mtv(subtractRes.subtractedExposure, title="Subtracted image")
921  mi = subtractRes.subtractedExposure.getMaskedImage()
922  x0, y0 = mi.getX0(), mi.getY0()
923  with disp.Buffering():
924  for s in diaSources:
925  x, y = s.getX() - x0, s.getY() - y0
926  ctype = "red" if s.get("flags_negative") else "yellow"
927  if (s.get("base_PixelFlags_flag_interpolatedCenter") or
928  s.get("base_PixelFlags_flag_saturatedCenter") or
929  s.get("base_PixelFlags_flag_crCenter")):
930  ptype = "x"
931  elif (s.get("base_PixelFlags_flag_interpolated") or
932  s.get("base_PixelFlags_flag_saturated") or
933  s.get("base_PixelFlags_flag_cr")):
934  ptype = "+"
935  else:
936  ptype = "o"
937  disp.dot(ptype, x, y, size=4, ctype=ctype)
938  lsstDebug.frame += 1
939 
940  if display and showPixelResiduals and selectSources:
941  nonKernelSources = []
942  for source in selectSources:
943  if source not in kernelSources:
944  nonKernelSources.append(source)
945 
946  diUtils.plotPixelResiduals(exposure,
947  subtractRes.warpedExposure,
948  subtractRes.subtractedExposure,
949  subtractRes.kernelCellSet,
950  subtractRes.psfMatchingKernel,
951  subtractRes.backgroundModel,
952  nonKernelSources,
953  self.subtract.config.kernel.active.detectionConfig,
954  origVariance=False)
955  diUtils.plotPixelResiduals(exposure,
956  subtractRes.warpedExposure,
957  subtractRes.subtractedExposure,
958  subtractRes.kernelCellSet,
959  subtractRes.psfMatchingKernel,
960  subtractRes.backgroundModel,
961  nonKernelSources,
962  self.subtract.config.kernel.active.detectionConfig,
963  origVariance=True)
964  if display and showDiaSources:
965  flagChecker = SourceFlagChecker(diaSources)
966  isFlagged = [flagChecker(x) for x in diaSources]
967  isDipole = [x.get("ip_diffim_ClassificationDipole_value") for x in diaSources]
968  diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
969  frame=lsstDebug.frame)
970  lsstDebug.frame += 1
971 
972  if display and showDipoles:
973  DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
974  frame=lsstDebug.frame)
975  lsstDebug.frame += 1
976 

Member Data Documentation

◆ algMetadata

lsst.pipe.tasks.imageDifference.ImageDifferenceTask.algMetadata

Definition at line 286 of file imageDifference.py.

◆ ConfigClass

lsst.pipe.tasks.imageDifference.ImageDifferenceTask.ConfigClass
static

Definition at line 256 of file imageDifference.py.

◆ RunnerClass

lsst.pipe.tasks.imageDifference.ImageDifferenceTask.RunnerClass
static

Definition at line 257 of file imageDifference.py.

◆ schema

lsst.pipe.tasks.imageDifference.ImageDifferenceTask.schema

Definition at line 278 of file imageDifference.py.


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