202 def run(self, sensorRef):
203 """Subtract an image from a template coadd and measure the result
206 - warp template coadd to match WCS of image
207 - PSF match image to warped template
208 - subtract image from PSF-matched, warped template
209 - persist difference image
213 @param sensorRef: sensor-level butler data reference, used for the following data products:
219 - self.config.coaddName + "Coadd_skyMap"
220 - self.config.coaddName + "Coadd"
221 Input or output, depending on config:
222 - self.config.coaddName + "Diff_subtractedExp"
223 Output, depending on config:
224 - self.config.coaddName + "Diff_matchedExp"
225 - self.config.coaddName + "Diff_src"
227 @return pipe_base Struct containing these fields:
228 - subtractedExposure: exposure after subtracting template;
229 the unpersisted version if subtraction not run but detection run
230 None if neither subtraction nor detection run (i.e. nothing useful done)
231 - subtractRes: results of subtraction task; None if subtraction not run
232 - sources: detected and possibly measured sources; None if detection not run
234 self.log.info(
"Processing %s" % (sensorRef.dataId))
237 subtractedExposure =
None
241 controlSources =
None
247 expBits = sensorRef.get(
"ccdExposureId_bits")
248 expId = long(sensorRef.get(
"ccdExposureId"))
249 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
252 exposure = sensorRef.get(
"calexp", immediate=
True)
253 if self.config.doAddCalexpBackground:
254 mi = exposure.getMaskedImage()
255 mi += sensorRef.get(
"calexpBackground").getImage()
256 if not exposure.hasPsf():
257 raise pipeBase.TaskError(
"Exposure has no psf")
258 sciencePsf = exposure.getPsf()
260 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp"
261 templateExposure =
None
262 templateSources =
None
263 if self.config.doSubtract:
264 templateExposure, templateSources = self.
getTemplate(exposure, sensorRef)
269 scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
273 psfAttr = PsfAttributes(templateExposure.getPsf(),
afwGeom.Point2I(ctr))
274 templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
280 if self.config.doPreConvolve:
283 srcMI = exposure.getMaskedImage()
284 destMI = srcMI.Factory(srcMI.getDimensions())
286 if self.config.useGaussianForPreConvolution:
288 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
289 preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
294 exposure.setMaskedImage(destMI)
295 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
297 scienceSigmaPost = scienceSigmaOrig
300 if self.config.doSelectSources:
301 if not sensorRef.datasetExists(
"src"):
302 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
304 selectSources = self.subtract.getSelectSources(
306 sigma = scienceSigmaPost,
307 doSmooth =
not self.doPreConvolve,
308 idFactory = idFactory,
311 self.log.info(
"Source selection via src product")
313 selectSources = sensorRef.get(
"src")
317 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
318 targetFwhmPix=templateSigma * FwhmPerSigma))
320 if self.config.doAddMetrics:
322 kcQa = KernelCandidateQa(nparam)
323 selectSources = kcQa.addToSchema(selectSources)
325 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
326 matches = astromRet.matches
327 kernelSources = self.sourceSelector.selectSources(exposure, selectSources, matches=matches)
328 random.shuffle(kernelSources, random.random)
329 controlSources = kernelSources[::self.config.controlStepSize]
330 kernelSources = [k
for i,k
in enumerate(kernelSources)
if i % self.config.controlStepSize]
332 if self.config.doSelectDcrCatalog:
333 redSelector = DiaCatalogSourceSelector(
334 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
335 redSources = redSelector.selectSources(exposure, selectSources, matches=matches)
336 controlSources.extend(redSources)
338 blueSelector = DiaCatalogSourceSelector(
339 DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
340 blueSources = blueSelector.selectSources(exposure, selectSources, matches=matches)
341 controlSources.extend(blueSources)
343 if self.config.doSelectVariableCatalog:
344 varSelector = DiaCatalogSourceSelector(
345 DiaCatalogSourceSelectorConfig(includeVariable=
True))
346 varSources = varSelector.selectSources(exposure, selectSources, matches=matches)
347 controlSources.extend(varSources)
349 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)"
350 % (len(kernelSources), len(selectSources), len(controlSources)))
352 if self.config.doUseRegister:
353 self.log.info(
"Registering images")
355 if templateSources
is None:
358 templateSources = self.subtract.getSelectSources(
367 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
368 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
369 exposure.getWcs(), exposure.getBBox())
370 templateExposure = warpedExp
375 if self.config.doDebugRegister:
377 srcToMatch = {x.second.getId() : x.first
for x
in matches}
379 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
380 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
381 sids = [m.first.getId()
for m
in wcsResults.matches]
382 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
383 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
384 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
385 allresids = dict(zip(sids, zip(positions, residuals)))
387 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
388 wcsResults.wcs.pixelToSky(
389 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
390 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g"))
391 + 2.5*numpy.log10(srcToMatch[x].get(
"r"))
392 for x
in sids
if x
in srcToMatch.keys()])
393 dlong = numpy.array([r[0].asArcseconds()
for s,r
in zip(sids, cresiduals)
394 if s
in srcToMatch.keys()])
395 dlat = numpy.array([r[1].asArcseconds()
for s,r
in zip(sids, cresiduals)
396 if s
in srcToMatch.keys()])
397 idx1 = numpy.where(colors<self.sourceSelector.config.grMin)
398 idx2 = numpy.where((colors>=self.sourceSelector.config.grMin)&
399 (colors<=self.sourceSelector.config.grMax))
400 idx3 = numpy.where(colors>self.sourceSelector.config.grMax)
401 rms1Long = IqrToSigma*(numpy.percentile(dlong[idx1],75)-numpy.percentile(dlong[idx1],25))
402 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1],75)-numpy.percentile(dlat[idx1],25))
403 rms2Long = IqrToSigma*(numpy.percentile(dlong[idx2],75)-numpy.percentile(dlong[idx2],25))
404 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2],75)-numpy.percentile(dlat[idx2],25))
405 rms3Long = IqrToSigma*(numpy.percentile(dlong[idx3],75)-numpy.percentile(dlong[idx3],25))
406 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3],75)-numpy.percentile(dlat[idx3],25))
407 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx1]),
409 numpy.median(dlat[idx1]),
411 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx2]),
413 numpy.median(dlat[idx2]),
415 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx3]),
417 numpy.median(dlat[idx3]),
420 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
421 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
422 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
423 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
424 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
425 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
427 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
428 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
429 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
430 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
431 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
432 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
439 self.log.info(
"Subtracting images")
440 subtractRes = self.subtract.subtractExposures(
441 templateExposure=templateExposure,
442 scienceExposure=exposure,
443 scienceFwhmPix=scienceSigmaPost * FwhmPerSigma,
444 templateFwhmPix=templateSigma * FwhmPerSigma,
445 candidateList=kernelSources,
446 convolveTemplate=self.config.convolveTemplate,
447 doWarping=
not self.config.doUseRegister
449 subtractedExposure = subtractRes.subtractedExposure
451 if self.config.doWriteMatchedExp:
452 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
454 if self.config.doDetection:
455 self.log.info(
"Running diaSource detection")
456 if subtractedExposure
is None:
457 subtractedExposure = sensorRef.get(subtractedExposureName)
460 if not subtractedExposure.hasPsf():
461 if self.config.convolveTemplate:
462 subtractedExposure.setPsf(exposure.getPsf())
464 if templateExposure
is None:
465 templateExposure, templateSources = self.
getTemplate(exposure, sensorRef)
466 subtractedExposure.setPsf(templateExposure.getPsf())
469 mask = subtractedExposure.getMaskedImage().getMask()
470 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
472 table = afwTable.SourceTable.make(self.
schema, idFactory)
474 results = self.detection.makeSourceCatalog(
476 exposure=subtractedExposure,
477 doSmooth=
not self.config.doPreConvolve
480 if self.config.doMerge:
481 fpSet = results.fpSets.positive
482 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
483 self.config.growFootprint,
False)
485 fpSet.makeSources(diaSources)
486 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
488 diaSources = results.sources
490 if self.config.doMeasurement:
491 if len(diaSources) < self.config.maxDiaSourcesToMeasure:
492 self.log.info(
"Running diaSource dipole measurement")
493 self.dipoleMeasurement.run(subtractedExposure, diaSources)
495 self.log.info(
"Running diaSource measurement")
496 self.measurement.run(subtractedExposure, diaSources)
499 if self.config.doMatchSources:
500 if sensorRef.datasetExists(
"src"):
502 matchRadAsec = self.config.diaSourceMatchRadius
503 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
505 srcMatches =
afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel,
True)
506 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for
507 srcMatch
in srcMatches])
508 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
511 self.log.warn(
"Src product does not exist; cannot match with diaSources")
515 refAstromConfig = measAstrom.AstrometryConfig()
516 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
517 refAstrometer = measAstrom.AstrometryTask(refAstromConfig)
518 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
519 refMatches = astromRet.matches
520 if refMatches
is None:
521 self.log.warn(
"No diaSource matches with reference catalog")
524 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
526 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for \
527 refMatch
in refMatches])
530 for diaSource
in diaSources:
531 sid = diaSource.getId()
532 if srcMatchDict.has_key(sid):
533 diaSource.set(
"srcMatchId", srcMatchDict[sid])
534 if refMatchDict.has_key(sid):
535 diaSource.set(
"refMatchId", refMatchDict[sid])
537 if diaSources
is not None and self.config.doWriteSources:
538 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
540 if self.config.doAddMetrics
and self.config.doSelectSources:
541 self.log.info(
"Evaluating metrics and control sample")
544 for cell
in subtractRes.kernelCellSet.getCellList():
545 for cand
in cell.begin(
False):
546 kernelCandList.append(cast_KernelCandidateF(cand))
549 basisList = afwMath.cast_LinearCombinationKernel(
550 kernelCandList[0].getKernel(KernelCandidateF.ORIG)).getKernelList()
553 diffimTools.sourceTableToCandidateList(controlSources,
554 subtractRes.warpedExposure, exposure,
555 self.config.subtract.kernel.active,
556 self.config.subtract.kernel.active.detectionConfig,
557 self.log, doBuild=
True, basisList=basisList)
559 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
561 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
563 if self.config.doDetection:
564 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
566 kcQa.aggregate(selectSources, self.metadata, allresids)
568 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
570 if self.config.doWriteSubtractedExp:
571 sensorRef.put(subtractedExposure, subtractedExposureName)
573 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
574 return pipeBase.Struct(
575 subtractedExposure=subtractedExposure,
576 subtractRes=subtractRes,