def subtractMaskedImages(self, templateMaskedImage, scienceMaskedImage, candidateList, templateFwhmPix=None, scienceFwhmPix=None): """!Psf-match and subtract two MaskedImages Do the following, in order: - PSF-match templateMaskedImage to scienceMaskedImage - Determine the differential background - Return the difference: scienceMaskedImage - ((warped templateMaskedImage convolved with psfMatchingKernel) + backgroundModel) @param templateMaskedImage: MaskedImage to PSF-match to scienceMaskedImage @param scienceMaskedImage: reference MaskedImage @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve) @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image @param candidateList: a list of footprints/maskedImages for kernel candidates; if None then source detection is run. - Currently supported: list of Footprints or measAlg.PsfCandidateF @return a pipeBase.Struct containing these fields: - subtractedMaskedImage = scienceMaskedImage - (matchedImage + backgroundModel) - matchedImage: templateMaskedImage convolved with psfMatchingKernel - psfMatchingKernel: PSF matching kernel - backgroundModel: differential background model - kernelCellSet: SpatialCellSet used to determine PSF matching kernel """ if not candidateList: raise RuntimeError( "Candidate list must be populated by makeCandidateList") results = self.matchMaskedImages( templateMaskedImage=templateMaskedImage, scienceMaskedImage=scienceMaskedImage, candidateList=candidateList, templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix, ) subtractedMaskedImage = afwImage.MaskedImageF(scienceMaskedImage, True) subtractedMaskedImage -= results.matchedImage subtractedMaskedImage -= results.backgroundModel results.subtractedMaskedImage = subtractedMaskedImage import lsstDebug display = lsstDebug.Info(__name__).display displayDiffIm = lsstDebug.Info(__name__).displayDiffIm maskTransparency = lsstDebug.Info(__name__).maskTransparency if not maskTransparency: maskTransparency = 0 if display: ds9.setMaskTransparency(maskTransparency) if display and displayDiffIm: ds9.mtv(subtractedMaskedImage, frame=lsstDebug.frame) lsstDebug.frame += 1 return results
def selectPsfSources(exposure, matches, psfPolicy): """Get a list of suitable stars to construct a PSF.""" import lsstDebug display = lsstDebug.Info(__name__).display displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells # # Unpack policy # kernelSize = psfPolicy.get("kernelSize") borderWidth = psfPolicy.get("borderWidth") sizePsfCellX = psfPolicy.get("sizeCellX") sizePsfCellY = psfPolicy.get("sizeCellY") # mi = exposure.getMaskedImage() if display and displayExposure: disp = afwDisplay.Display(frame=0) disp.mtv(mi, title="PSF candidates") psfCellSet = afwMath.SpatialCellSet(mi.getBBox(), sizePsfCellX, sizePsfCellY) psfStars = [] for val in matches: ref, source = val[0:2] if not (ref.getFlagForDetection() & measAlg.Flags.STAR) or \ (source.getFlagForDetection() & measAlg.Flags.BAD): continue try: cand = measAlg.makePsfCandidate(source, mi) # # The setXXX methods are class static, but it's convenient to call them on # an instance as we don't know Exposure's pixel type (and hence cand's exact type) if cand.getWidth() == 0: cand.setBorderWidth(borderWidth) cand.setWidth(kernelSize + 2*borderWidth) cand.setHeight(kernelSize + 2*borderWidth) im = cand.getMaskedImage().getImage() max = afwMath.makeStatistics(im, afwMath.MAX).getValue() if not np.isfinite(max): continue psfCellSet.insertCandidate(cand) if display and displayExposure: disp.dot("+", source.getXAstrom() - mi.getX0(), source.getYAstrom() - mi.getY0(), size=4, ctype=afwDisplay.CYAN) disp.dot("o", source.getXAstrom() - mi.getX0(), source.getYAstrom() - mi.getY0(), size=4, ctype=afwDisplay.CYAN) except Exception: continue source.setFlagForDetection(source.getFlagForDetection() | measAlg.Flags.STAR) psfStars += [source] return psfStars, psfCellSet
def distort(self, sourceCat, exposure): """!Calculate distorted source positions CCD images are often affected by optical distortion that makes the astrometric solution higher order than linear. Unfortunately, most (all?) matching algorithms require that the distortion be small or zero, and so it must be removed. We do this by calculating (un-)distorted positions, based on a known optical distortion model in the Ccd. The distortion correction moves sources, so we return the distorted bounding box. \param[in] exposure Exposure to process \param[in,out] sourceCat SourceCatalog; getX() and getY() will be used as inputs, with distorted points in "centroid.distorted" field. \return bounding box of distorted exposure """ detector = exposure.getDetector() pixToTanXYTransform = None if detector is None: self.log.warn( "No detector associated with exposure; assuming null distortion" ) else: tanSys = detector.makeCameraSys(TAN_PIXELS) pixToTanXYTransform = detector.getTransformMap().get(tanSys) if pixToTanXYTransform is None: self.log.info("Null distortion correction") for s in sourceCat: s.set(self.centroidKey, s.getCentroid()) s.set(self.centroidErrKey, s.getCentroidErr()) s.set(self.centroidFlagKey, s.getCentroidFlag()) return exposure.getBBox() # Distort source positions self.log.info("Applying distortion correction") for s in sourceCat: centroid = pixToTanXYTransform.forwardTransform(s.getCentroid()) s.set(self.centroidKey, centroid) s.set(self.centroidErrKey, s.getCentroidErr()) s.set(self.centroidFlagKey, s.getCentroidFlag()) # Get distorted image size so that astrometry_net does not clip. bboxD = afwGeom.Box2D() for corner in detector.getCorners(TAN_PIXELS): bboxD.include(corner) if lsstDebug.Info(__name__).display: frame = lsstDebug.Info(__name__).frame pause = lsstDebug.Info(__name__).pause displayAstrometry(sourceCat=sourceCat, distortedCentroidKey=self.centroidKey, exposure=exposure, frame=frame, pause=pause) return afwGeom.Box2I(bboxD)
def displayDipoles(self, exposure, sources): """!Display debugging information on the detected dipoles @param exposure Image the dipoles were measured on @param sources The set of diaSources that were measured""" import lsstDebug display = lsstDebug.Info(__name__).display displayDiaSources = lsstDebug.Info(__name__).displayDiaSources maskTransparency = lsstDebug.Info(__name__).maskTransparency if not maskTransparency: maskTransparency = 90 ds9.setMaskTransparency(maskTransparency) ds9.mtv(exposure, frame=lsstDebug.frame) if display and displayDiaSources: with ds9.Buffering(): for source in sources: cenX, cenY = source.get("ipdiffim_DipolePsfFlux_centroid") if np.isinf(cenX) or np.isinf(cenY): cenX, cenY = source.getCentroid() isdipole = source.get("classification.dipole") if isdipole and np.isfinite(isdipole): # Dipole ctype = "green" else: # Not dipole ctype = "red" ds9.dot("o", cenX, cenY, size=2, ctype=ctype, frame=lsstDebug.frame) negCenX = source.get( "ip_diffim_PsfDipoleFlux_neg_centroid_x") negCenY = source.get( "ip_diffim_PsfDipoleFlux_neg_centroid_y") posCenX = source.get( "ip_diffim_PsfDipoleFlux_pos_centroid_x") posCenY = source.get( "ip_diffim_PsfDipoleFlux_pos_centroid_y") if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX) or np.isinf(posCenY)): continue ds9.line([(negCenX, negCenY), (posCenX, posCenY)], ctype="yellow", frame=lsstDebug.frame) lsstDebug.frame += 1
def displayDipoles(self, exposure, sources): """Display debugging information on the detected dipoles Parameters ---------- exposure : `lsst.afw.image.Exposure` Image the dipoles were measured on sources : `lsst.afw.table.SourceCatalog` The set of diaSources that were measured""" import lsstDebug display = lsstDebug.Info(__name__).display displayDiaSources = lsstDebug.Info(__name__).displayDiaSources maskTransparency = lsstDebug.Info(__name__).maskTransparency if not maskTransparency: maskTransparency = 90 disp = afwDisplay.Display(frame=lsstDebug.frame) disp.setMaskTransparency(maskTransparency) disp.mtv(exposure) if display and displayDiaSources: with disp.Buffering(): for source in sources: cenX, cenY = source.get("ipdiffim_DipolePsfFlux_centroid") if np.isinf(cenX) or np.isinf(cenY): cenX, cenY = source.getCentroid() isdipole = source.get( "ip_diffim_ClassificationDipole_value") if isdipole and np.isfinite(isdipole): # Dipole ctype = afwDisplay.GREEN else: # Not dipole ctype = afwDisplay.RED disp.dot("o", cenX, cenY, size=2, ctype=ctype) negCenX = source.get( "ip_diffim_PsfDipoleFlux_neg_centroid_x") negCenY = source.get( "ip_diffim_PsfDipoleFlux_neg_centroid_y") posCenX = source.get( "ip_diffim_PsfDipoleFlux_pos_centroid_x") posCenY = source.get( "ip_diffim_PsfDipoleFlux_pos_centroid_y") if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX) or np.isinf(posCenY)): continue disp.line([(negCenX, negCenY), (posCenX, posCenY)], ctype=afwDisplay.YELLOW) lsstDebug.frame += 1
def select(self, exposure, matches, psfPolicy): """Get a list of suitable stars to construct a PSF.""" import lsstDebug display = lsstDebug.Info(__name__).display displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells # # Unpack policy # kernelSize = psfPolicy["kernelSizeMin"] borderWidth = psfPolicy["borderWidth"] # mi = exposure.getMaskedImage() if display and displayExposure: frame = 0 ds9.mtv(mi, frame=frame, title="PSF candidates") psfCandidates = [] for val in matches: ref, source = val[0:2] if not (ref.getFlagForDetection() & measAlg.Flags.STAR) or \ (source.getFlagForDetection() & measAlg.Flags.BAD): continue if source.getPsfFlux() <= 0.0: continue try: cand = measAlg.makePsfCandidate(source, mi) # # The setXXX methods are class static, but it's convenient to call them on # an instance as we don't know Exposure's pixel type (and hence cand's exact type) if cand.getWidth() == 0: cand.setBorderWidth(borderWidth) cand.setWidth(kernelSize + 2*borderWidth) cand.setHeight(kernelSize + 2*borderWidth) im = cand.getImage().getImage() max = afwMath.makeStatistics(im, afwMath.MAX).getValue() if not numpy.isfinite(max): continue if display and displayExposure: ds9.dot("+", source.getXAstrom() - mi.getX0(), source.getYAstrom() - mi.getY0(), size=4, frame=frame, ctype=ds9.CYAN) ds9.dot("o", source.getXAstrom() - mi.getX0(), source.getYAstrom() - mi.getY0(), size=4, frame=frame, ctype=ds9.CYAN) except Exception, e: continue source.setFlagForDetection(source.getFlagForDetection() | measAlg.Flags.STAR) psfCandidates.append(cand)
def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None): """ @param[in] exposure: exposure containing the psf candidates (lsst.afw.image.Exposure) @param[in] psfCandidateList: a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate); typically obtained by detecting sources and then running them through a star selector @param[in,out] metadata a home for interesting tidbits of information @param[in] flagKey: schema key used to mark sources actually used in PSF determination @return psf """ import lsstDebug display = lsstDebug.Info(__name__).display displayExposure = display and \ lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells displayPsfCandidates = display and \ lsstDebug.Info(__name__).displayPsfCandidates # show the viable candidates displayPsfComponents = display and \ lsstDebug.Info(__name__).displayPsfComponents # show the basis functions showBadCandidates = display and \ lsstDebug.Info(__name__).showBadCandidates # Include bad candidates (meaningless, methinks) displayResiduals = display and \ lsstDebug.Info(__name__).displayResiduals # show residuals displayPsfMosaic = display and \ lsstDebug.Info(__name__).displayPsfMosaic # show mosaic of reconstructed PSF(x,y) matchKernelAmplitudes = lsstDebug.Info(__name__).matchKernelAmplitudes # match Kernel amplitudes for spatial plots normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals # Normalise residuals by object amplitude mi = exposure.getMaskedImage() nCand = len(psfCandidateList) if nCand == 0: raise RuntimeError("No PSF candidates supplied.") # # How big should our PSF models be? # if display: # only needed for debug plots # construct and populate a spatial cell set bbox = mi.getBBox(afwImage.PARENT) psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY) else: psfCellSet = None sizes = np.empty(nCand) for i, psfCandidate in enumerate(psfCandidateList): try: if psfCellSet: psfCellSet.insertCandidate(psfCandidate) except Exception, e: self.debugLog.debug(2, "Skipping PSF candidate %d of %d: %s" % (i, len(psfCandidateList), e)) continue source = psfCandidate.getSource() quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy()) rmsSize = quad.getTraceRadius() sizes[i] = rmsSize
def cosmicray(self, exposure, psf): """Cosmic ray masking @param exposure Exposure to process @param psf PSF """ import lsstDebug display = lsstDebug.Info(__name__).display displayCR = lsstDebug.Info(__name__).displayCR assert exposure, "No exposure provided" assert psf, "No psf provided" # Blow away old mask try: mask = exposure.getMaskedImage().getMask() crBit = mask.getMaskPlane("CR") mask.clearMaskPlane(crBit) except: pass if display and displayCR: ds9.incrDefaultFrame() ds9.mtv(exposure, title="Pre-CR") policy = self.config['cosmicray'].getPolicy() mi = exposure.getMaskedImage() bg = afwMath.makeStatistics(mi, afwMath.MEDIAN).getValue() crs = measAlg.findCosmicRays(mi, psf, bg, policy, self._keepCRs) num = 0 if crs is not None: mask = mi.getMask() crBit = mask.getPlaneBitMask("CR") afwDet.setMaskFromFootprintList(mask, crs, crBit) num = len(crs) if display and displayCR: ds9.incrDefaultFrame() ds9.mtv(exposure, title="Post-CR") ds9.cmdBuffer.pushSize() for cr in crs: displayUtils.drawBBox(cr.getBBox(), borderWidth=0.55) ds9.cmdBuffer.popSize() self.log.log(self.log.INFO, "Identified %d cosmic rays." % num) return
def __init__(self, config=None, name=None, parentTask=None, log=None): self.metadata = dafBase.PropertyList() self._parentTask = parentTask if parentTask is not None: if name is None: raise RuntimeError("name is required for a subtask") self._name = name self._fullName = parentTask._computeFullName(name) if config is None: config = getattr(parentTask.config, name) self._taskDict = parentTask._taskDict loggerName = parentTask.log.getName() + '.' + name else: if name is None: name = getattr(self, "_DefaultName", None) if name is None: raise RuntimeError( "name is required for a task unless it has attribute _DefaultName" ) name = self._DefaultName self._name = name self._fullName = self._name if config is None: config = self.ConfigClass() self._taskDict = dict() loggerName = self._fullName if log is not None and log.getName(): loggerName = log.getName() + '.' + loggerName self.log = Log.getLogger(loggerName) self.config = config self._display = lsstDebug.Info(self.__module__).display self._taskDict[self._fullName] = self
def run(self, dataRef): """!Compute a few statistics on the image plane of an exposure @param dataRef: data reference for a calibrated science exposure ("calexp") @return a pipeBase Struct containing: - mean: mean of image plane - meanErr: uncertainty in mean - stdDev: standard deviation of image plane - stdDevErr: uncertainty in standard deviation """ self.log.info("Processing data ID %s" % (dataRef.dataId, )) if self.config.doFail: raise pipeBase.TaskError( "Raising TaskError by request (config.doFail=True)") # Unpersist the raw exposure pointed to by the data reference rawExp = dataRef.get("raw") maskedImage = rawExp.getMaskedImage() # Support extra debug output. # - import lsstDebug display = lsstDebug.Info(__name__).display if display: frame = 1 mtv(rawExp, frame=frame, title="exposure") # return the pipe_base Struct that is returned by self.stats.run return self.stats.run(maskedImage)
def __init__( self, config: Optional[Config] = None, name: Optional[str] = None, parentTask: Optional[Task] = None, log: Optional[Union[logging.Logger, lsst.utils.logging.LsstLogAdapter]] = None, ): self.metadata = _TASK_METADATA_TYPE() self.__parentTask: Optional[weakref.ReferenceType] self.__parentTask = parentTask if parentTask is None else weakref.ref( parentTask) if parentTask is not None: if name is None: raise RuntimeError("name is required for a subtask") self._name = name self._fullName = parentTask._computeFullName(name) if config is None: config = getattr(parentTask.config, name) self._taskDict: Dict[ str, weakref.ReferenceType[Task]] = parentTask._taskDict loggerName = parentTask.log.getChild(name).name else: if name is None: name = getattr(self, "_DefaultName", None) if name is None: raise RuntimeError( "name is required for a task unless it has attribute _DefaultName" ) name = self._DefaultName self._name = name self._fullName = self._name if config is None: config = self.ConfigClass() self._taskDict = dict() loggerName = self._fullName if log is not None and log.name: loggerName = log.getChild(loggerName).name elif self._add_module_logger_prefix: # Prefix the logger name with the root module name. # We want all Task loggers to have this prefix to make # it easier to control them. This can be disabled by # a Task setting the class property _add_module_logger_prefix # to False -- in which case the logger name will not be # modified. module_name = self.__module__ module_root = module_name.split(".")[0] + "." if not loggerName.startswith(module_root): loggerName = module_root + loggerName # Get a logger (that might be a subclass of logging.Logger). self.log: lsst.utils.logging.LsstLogAdapter = lsst.utils.logging.getLogger( loggerName) self.config: Config = config if lsstDebug: self._display = lsstDebug.Info(self.__module__).display else: self._display = None self._taskDict[self._fullName] = weakref.ref(self)
def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True): """Detect footprints. @param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place. @param doSmooth if True, smooth the image before detection using a Gaussian of width sigma @param sigma sigma of PSF (pixels); used for smoothing and to grow detections; if None then measure the sigma of the PSF of the exposure @param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection @return a lsst.pipe.base.Struct with fields: - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None) - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None) - numPos: number of footprints in positive or 0 if detection polarity was negative - numNeg: number of footprints in negative or 0 if detection polarity was positive - background: re-estimated background. None if reEstimateBackground==False @raise pipe_base TaskError if sigma=None and the exposure has no PSF """ try: import lsstDebug display = lsstDebug.Info(__name__).display except ImportError, e: try: display except NameError: display = False
def estimateBackground(exposure, backgroundConfig, subtract=True, stats=True, statsKeys=None): """ Estimate exposure's background using parameters in backgroundConfig. If subtract is true, make a copy of the exposure and subtract the background. If `stats` is True, measure the mean and variance of the background and add them to the background-subtracted exposure's metadata with keys "BGMEAN" and "BGVAR", or the keys given in `statsKeys` (2-tuple of strings). Return background, backgroundSubtractedExposure """ displayBackground = lsstDebug.Info(__name__).displayBackground maskedImage = exposure.getMaskedImage() background = getBackground(maskedImage, backgroundConfig) if not background: raise RuntimeError, "Unable to estimate background for exposure" bgimg = None if displayBackground > 1: bgimg = background.getImageF() ds9.mtv(bgimg, title="background", frame=3) if not subtract: return background, None bbox = maskedImage.getBBox(afwImage.PARENT) backgroundSubtractedExposure = exposure.Factory(exposure, bbox, afwImage.PARENT, True) copyImage = backgroundSubtractedExposure.getMaskedImage().getImage() if bgimg is None: bgimg = background.getImageF() copyImage -= bgimg # Record statistics of the background in the bgsub exposure metadata. # (lsst.daf.base.PropertySet) if stats: if statsKeys is None: mnkey = 'BGMEAN' varkey = 'BGVAR' else: mnkey, varkey = statsKeys meta = backgroundSubtractedExposure.getMetadata() s = afwMath.makeStatistics(bgimg, afwMath.MEAN | afwMath.VARIANCE) bgmean = s.getValue(afwMath.MEAN) bgvar = s.getValue(afwMath.VARIANCE) meta.addDouble(mnkey, bgmean) meta.addDouble(varkey, bgvar) if displayBackground: ds9.mtv(backgroundSubtractedExposure, title="subtracted") return background, backgroundSubtractedExposure
def debugView(self, image, model): """Debug display for the final overscan solution. Parameters ---------- image : `lsst.afw.image.Image` Input image the overscan solution was determined from. model : `numpy.ndarray` or `float` Overscan model determined for the image. """ import lsstDebug if not lsstDebug.Info(__name__).display: return calcImage = self.getImageArray(image) calcImage, isTransposed = self.transpose(calcImage) masked = self.maskOutliers(calcImage) collapsed = self.collapseArray(masked) num = len(collapsed) indices = 2.0 * np.arange(num) / float(num) - 1.0 if np.ma.is_masked(collapsed): collapsedMask = collapsed.mask else: collapsedMask = np.array(num * [np.ma.nomask]) import matplotlib.pyplot as plot figure = plot.figure(1) figure.clear() axes = figure.add_axes((0.1, 0.1, 0.8, 0.8)) axes.plot(indices[~collapsedMask], collapsed[~collapsedMask], 'k+') if collapsedMask.sum() > 0: axes.plot(indices[collapsedMask], collapsed.data[collapsedMask], 'b+') if isinstance(model, np.ndarray): plotModel = model else: plotModel = np.zeros_like(indices) plotModel += model axes.plot(indices, plotModel, 'r-') plot.xlabel("centered/scaled position along overscan region") plot.ylabel("pixel value/fit value") figure.show() prompt = "Press Enter or c to continue [chp]..." while True: ans = input(prompt).lower() if ans in ( "", " ", "c", ): break elif ans in ("p", ): import pdb pdb.set_trace() elif ans in ("h", ): print("[h]elp [c]ontinue [p]db") plot.close()
def loadAndMatch(self, exposure, sourceCat): """!Load reference objects overlapping an exposure and match to sources detected on that exposure @param[in] exposure exposure that the sources overlap @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog) @return an lsst.pipe.base.Struct with these fields: - refCat reference object catalog of objects that overlap the exposure (with some margin) (an lsst::afw::table::SimpleCatalog) - matches a list of lsst.afw.table.ReferenceMatch - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList) @note ignores config.matchDistanceSigma """ import lsstDebug debug = lsstDebug.Info(__name__) matchMeta = createMatchMetadata( exposure, border=self.refObjLoader.config.pixelMargin) expMd = self._getExposureMetadata(exposure) loadRes = self.refObjLoader.loadPixelBox( bbox=expMd.bbox, wcs=expMd.wcs, filterName=expMd.filterName, calib=expMd.calib, ) matchRes = self.matcher.matchObjectsToSources( refCat=loadRes.refCat, sourceCat=sourceCat, wcs=expMd.wcs, refFluxField=loadRes.fluxField, match_tolerance=None, ) distStats = self._computeMatchStatsOnSky(matchRes.matches) self.log.info( "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " % (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds())) if debug.display: frame = int(debug.frame) displayAstrometry( refCat=loadRes.refCat, sourceCat=sourceCat, matches=matchRes.matches, exposure=exposure, bbox=expMd.bbox, frame=frame, title="Matches", ) return pipeBase.Struct( refCat=loadRes.refCat, matches=matchRes.matches, matchMeta=matchMeta, )
def run(self, exposure, fringes, seed=None): """Remove fringes from the provided science exposure. Primary method of FringeTask. Fringes are only subtracted if the science exposure has a filter listed in the configuration. Parameters ---------- exposure : `lsst.afw.image.Exposure` Science exposure from which to remove fringes. fringes : `lsst.afw.image.Exposure` or `list` thereof Calibration fringe files containing master fringe frames. seed : `int`, optional Seed for random number generation. Returns ------- solution : `np.array` Fringe solution amplitudes for each input fringe frame. rms : `float` RMS error for the fit solution for this exposure. """ import lsstDebug display = lsstDebug.Info(__name__).display if not self.checkFilter(exposure): self.log.info( "Filter not found in FringeTaskConfig.filters. Skipping fringe correction." ) return if seed is None: seed = self.config.stats.rngSeedOffset rng = numpy.random.RandomState(seed=seed) if not hasattr(fringes, '__iter__'): fringes = [fringes] mask = exposure.getMaskedImage().getMask() for fringe in fringes: fringe.getMaskedImage().getMask().__ior__(mask) if self.config.pedestal: self.removePedestal(fringe) positions = self.generatePositions(fringes[0], rng) fluxes = numpy.ndarray([self.config.num, len(fringes)]) for i, f in enumerate(fringes): fluxes[:, i] = self.measureExposure(f, positions, title="Fringe frame") expFringes = self.measureExposure(exposure, positions, title="Science") solution, rms = self.solve(expFringes, fluxes) self.subtract(exposure, fringes, solution) if display: afwDisplay.Display(frame=getFrame()).mtv(exposure, title="Fringe subtracted") return solution, rms
def measureExposure(self, exposure, positions, title="Fringe"): """Measure fringe amplitudes for an exposure The fringe amplitudes are measured as the statistic within a square aperture. The statistic within a larger aperture are subtracted so as to remove the background. Parameters ---------- exposure : `lsst.afw.image.Exposure` Exposure to measure the positions on. positions : `numpy.array` Two-dimensional array containing the positions to sample for fringe amplitudes. title : `str`, optional Title used for debug out plots. Returns ------- fringes : `numpy.array` Array of measured exposure values at each of the positions supplied. """ stats = afwMath.StatisticsControl() stats.setNumSigmaClip(self.config.stats.clip) stats.setNumIter(self.config.stats.iterations) stats.setAndMask(exposure.getMaskedImage().getMask().getPlaneBitMask( self.config.stats.badMaskPlanes)) num = self.config.num fringes = numpy.ndarray(num) for i in range(num): x, y = positions[i] small = measure(exposure.getMaskedImage(), x, y, self.config.small, self.config.stats.stat, stats) large = measure(exposure.getMaskedImage(), x, y, self.config.large, self.config.stats.stat, stats) fringes[i] = small - large import lsstDebug display = lsstDebug.Info(__name__).display if display: disp = afwDisplay.Display(frame=getFrame()) disp.mtv(exposure, title=title) if False: with disp.Buffering(): for x, y in positions: corners = numpy.array([[-1, -1], [1, -1], [1, 1], [-1, 1], [-1, -1]]) + [[x, y]] disp.line(corners * self.config.small, ctype=afwDisplay.GREEN) disp.line(corners * self.config.large, ctype=afwDisplay.BLUE) return fringes
def display(self, exposure, results, convolvedImage=None): """Display detections if so configured Displays the ``exposure`` in frame 0, overlays the detection peaks. Requires that ``lsstDebug`` has been set up correctly, so that ``lsstDebug.Info("lsst.meas.algorithms.detection")`` evaluates `True`. If the ``convolvedImage`` is non-`None` and ``lsstDebug.Info("lsst.meas.algorithms.detection") > 1``, the ``convolvedImage`` will be displayed in frame 1. Parameters ---------- exposure : `lsst.afw.image.Exposure` Exposure to display, on which will be plotted the detections. results : `lsst.pipe.base.Struct` Results of the 'detectFootprints' method, containing positive and negative footprints (which contain the peak positions that we will plot). This is a `Struct` with ``positive`` and ``negative`` elements that are of type `lsst.afw.detection.FootprintSet`. convolvedImage : `lsst.afw.image.Image`, optional Convolved image used for thresholding. """ try: import lsstDebug display = lsstDebug.Info(__name__).display except ImportError: try: display except NameError: display = False if not display: return afwDisplay.setDefaultMaskTransparency(75) disp0 = afwDisplay.Display(frame=0) disp0.mtv(exposure, title="detection") def plotPeaks(fps, ctype): if fps is None: return with disp0.Buffering(): for fp in fps.getFootprints(): for pp in fp.getPeaks(): disp0.dot("+", pp.getFx(), pp.getFy(), ctype=ctype) plotPeaks(results.positive, "yellow") plotPeaks(results.negative, "red") if convolvedImage and display > 1: disp1 = afwDisplay.Display(frame=1) disp1.mtv(convolvedImage, title="PSF smoothed") disp2 = afwDisplay.Display(frame=2) disp2.mtv(afwImage.ImageF(np.sqrt(exposure.variance.array)), title="stddev")
def __init__(self, config=None, name=None, parentTask=None, log=None): """!Create a Task @param[in] config configuration for this task (an instance of self.ConfigClass, which is a task-specific subclass of lsst.pex.config.Config), or None. If None: - If parentTask specified then defaults to parentTask.config.\<name> - If parentTask is None then defaults to self.ConfigClass() @param[in] name brief name of task, or None; if None then defaults to self._DefaultName @param[in] parentTask the parent task of this subtask, if any. - If None (a top-level task) then you must specify config and name is ignored. - If not None (a subtask) then you must specify name @param[in] log log (an lsst.log.Log) whose name is used as a log name prefix, or None for no prefix. Ignored if parentTask specifie, in which case parentTask.log's name is used as a prefix. The task's log name is `prefix + "." + name` if a prefix exists, else `name`. The task's log is then a child logger of parentTask.log (if parentTask specified), or a child logger of the log from the argument (if log is not None). @throw RuntimeError if parentTask is None and config is None. @throw RuntimeError if parentTask is not None and name is None. @throw RuntimeError if name is None and _DefaultName does not exist. """ self.metadata = dafBase.PropertyList() self._parentTask = parentTask if parentTask is not None: if name is None: raise RuntimeError("name is required for a subtask") self._name = name self._fullName = parentTask._computeFullName(name) if config is None: config = getattr(parentTask.config, name) self._taskDict = parentTask._taskDict loggerName = parentTask.log.getName() + '.' + name else: if name is None: name = getattr(self, "_DefaultName", None) if name is None: raise RuntimeError("name is required for a task unless it has attribute _DefaultName") name = self._DefaultName self._name = name self._fullName = self._name if config is None: config = self.ConfigClass() self._taskDict = dict() loggerName = self._fullName if log is not None and log.getName(): loggerName = log.getName() + '.' + loggerName self.log = Log.getLogger(loggerName) self.config = config self._display = lsstDebug.Info(self.__module__).display self._taskDict[self._fullName] = self
def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground): """Provide visualization of the inputs and ouputs to the Psf-matching code Parameters ---------- kernelCellSet : `lsst.afw.math.SpatialCellSet` The SpatialCellSet used in determining the matching kernel and background spatialKernel : `lsst.afw.math.LinearCombinationKernel` Spatially varying Psf-matching kernel spatialBackground : `lsst.afw.math.Function2D` Spatially varying background-matching function """ import lsstDebug displayCandidates = lsstDebug.Info(__name__).displayCandidates displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic plotKernelSpatialModel = lsstDebug.Info( __name__).plotKernelSpatialModel showBadCandidates = lsstDebug.Info(__name__).showBadCandidates maskTransparency = lsstDebug.Info(__name__).maskTransparency if not maskTransparency: maskTransparency = 0 afwDisplay.setDefaultMaskTransparency(maskTransparency) if displayCandidates: diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, frame=lsstDebug.frame, showBadCandidates=showBadCandidates) lsstDebug.frame += 1 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, frame=lsstDebug.frame, showBadCandidates=showBadCandidates, kernels=True) lsstDebug.frame += 1 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, frame=lsstDebug.frame, showBadCandidates=showBadCandidates, resids=True) lsstDebug.frame += 1 if displayKernelBasis: diutils.showKernelBasis(spatialKernel, frame=lsstDebug.frame) lsstDebug.frame += 1 if displayKernelMosaic: diutils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame) lsstDebug.frame += 1 if plotKernelSpatialModel: diutils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
def selectStars(self, exposure, sourceCat, matches=None): """!Return a list of PSF candidates that represent likely stars A list of PSF candidates may be used by a PSF fitter to construct a PSF. @param[in] exposure the exposure containing the sources @param[in] sourceCat catalog of sources that may be stars (an lsst.afw.table.SourceCatalog) @param[in] matches a match vector as produced by meas_astrom; required (defaults to None to match the StarSelector API and improve error handling) @return an lsst.pipe.base.Struct containing: - starCat catalog of selected stars (a subset of sourceCat) """ import lsstDebug debugInfo = lsstDebug.Info(__name__) display = debugInfo.display pauseAtEnd = debugInfo.pauseAtEnd # pause when done if matches is None: raise RuntimeError("CatalogStarSelectorTask requires matches") mi = exposure.getMaskedImage() if display: frame = 1 ds9.mtv(mi, frame=frame, title="PSF candidates") isGoodSource = CheckSource(sourceCat, self.config.fluxLim, self.config.fluxMax, self.config.badFlags) starCat = SourceCatalog(sourceCat.schema) with ds9.Buffering(): for ref, source, d in matches: if not ref.get("resolved"): if not isGoodSource(source): symb, ctype = "+", ds9.RED else: starCat.append(source) symb, ctype = "+", ds9.GREEN if display: ds9.dot(symb, source.getX() - mi.getX0(), source.getY() - mi.getY0(), size=4, frame=frame, ctype=ctype) if display and pauseAtEnd: input("Continue? y[es] p[db] ") return Struct(starCat=starCat, )
def getBackground(image, backgroundConfig, nx=0, ny=0, algorithm=None): """ Make a new Exposure which is exposure - background """ backgroundConfig.validate() if not nx: nx = image.getWidth() // backgroundConfig.binSize + 1 if not ny: ny = image.getHeight() // backgroundConfig.binSize + 1 displayBackground = lsstDebug.Info(__name__).displayBackground if displayBackground: import itertools ds9.mtv(image, frame=1) xPosts = numpy.rint( numpy.linspace(0, image.getWidth() + 1, num=nx, endpoint=True)) yPosts = numpy.rint( numpy.linspace(0, image.getHeight() + 1, num=ny, endpoint=True)) with ds9.Buffering(): for (xMin, xMax), (yMin, yMax) in itertools.product( zip(xPosts[:-1], xPosts[1:]), zip(yPosts[:-1], yPosts[1:])): ds9.line([(xMin, yMin), (xMin, yMax), (xMax, yMax), (xMax, yMin), (xMin, yMin)], frame=1) sctrl = afwMath.StatisticsControl() sctrl.setAndMask( reduce(lambda x, y: x | image.getMask().getPlaneBitMask(y), backgroundConfig.ignoredPixelMask, 0x0)) sctrl.setNanSafe(backgroundConfig.isNanSafe) pl = pexLogging.Debug("meas.utils.sourceDetection.getBackground") pl.debug( 3, "Ignoring mask planes: %s" % ", ".join(backgroundConfig.ignoredPixelMask)) if not algorithm: algorithm = backgroundConfig.algorithm bctrl = afwMath.BackgroundControl(algorithm, nx, ny, backgroundConfig.undersampleStyle, sctrl, backgroundConfig.statisticsProperty) if backgroundConfig.useApprox: actrl = afwMath.ApproximateControl( afwMath.ApproximateControl.CHEBYSHEV, backgroundConfig.approxOrder) bctrl.setApproximateControl(actrl) return afwMath.makeBackground(image, bctrl)
def loadAndMatch(self, exposure, sourceCat, bbox=None): """!Load reference objects overlapping an exposure and match to sources detected on that exposure @param[in] exposure exposure whose WCS is to be fit @param[in] sourceCat catalog of sourceCat detected on the exposure (an lsst.afw.table.SourceCatalog) @param[in] bbox bounding box go use for finding reference objects; if None, use exposure's bbox @return an lsst.pipe.base.Struct with these fields: - refCat reference object catalog of objects that overlap the exposure (with some margin) (an lsst::afw::table::SimpleCatalog) - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch - matchMeta metadata about the field (an lsst.daf.base.PropertyList) @note ignores config.forceKnownWcs """ with self.distortionContext(sourceCat=sourceCat, exposure=exposure) as bbox: if not self.solver: self.makeSubtask("solver") astrom = self.solver.useKnownWcs( sourceCat=sourceCat, exposure=exposure, bbox=bbox, calculateSip=False, ) if astrom is None or astrom.getWcs() is None: raise RuntimeError("Unable to solve astrometry") matches = astrom.getMatches() matchMeta = astrom.getMatchMetadata() if matches is None or len(matches) == 0: raise RuntimeError("No astrometric matches") self.log.info("%d astrometric matches" % (len(matches))) if self._display: frame = lsstDebug.Info(__name__).frame displayAstrometry(exposure=exposure, sourceCat=sourceCat, matches=matches, frame=frame, pause=False) return pipeBase.Struct( refCat=astrom.refCat, matches=matches, matchMeta=matchMeta, )
def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground): """!Provide visualization of the inputs and ouputs to the Psf-matching code @param kernelCellSet: the SpatialCellSet used in determining the matching kernel and background @param spatialKernel: spatially varying Psf-matching kernel @param spatialBackground: spatially varying background-matching function """ import lsstDebug displayCandidates = lsstDebug.Info(__name__).displayCandidates displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic plotKernelSpatialModel = lsstDebug.Info( __name__).plotKernelSpatialModel showBadCandidates = lsstDebug.Info(__name__).showBadCandidates maskTransparency = lsstDebug.Info(__name__).maskTransparency if not maskTransparency: maskTransparency = 0 ds9.setMaskTransparency(maskTransparency) if displayCandidates: diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, frame=lsstDebug.frame, showBadCandidates=showBadCandidates) lsstDebug.frame += 1 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, frame=lsstDebug.frame, showBadCandidates=showBadCandidates, kernels=True) lsstDebug.frame += 1 diutils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, frame=lsstDebug.frame, showBadCandidates=showBadCandidates, resids=True) lsstDebug.frame += 1 if displayKernelBasis: diutils.showKernelBasis(spatialKernel, frame=lsstDebug.frame) lsstDebug.frame += 1 if displayKernelMosaic: diutils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame) lsstDebug.frame += 1 if plotKernelSpatialModel: diutils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
def _astrometry(self, sourceCat, exposure, bbox=None): """!Solve astrometry to produce WCS \param[in] sourceCat Sources on exposure, an lsst.afw.table.SourceCatalog \param[in,out] exposure Exposure to process, an lsst.afw.image.ExposureF or D; wcs is updated \param[in] bbox Bounding box, or None to use exposure \return a pipe.base.Struct with fields: - refCat reference object catalog of objects that overlap the exposure (with some margin) (an lsst::afw::table::SimpleCatalog) - matches astrometric matches, a list of lsst.afw.table.ReferenceMatch - matchMeta metadata about the field (an lsst.daf.base.PropertyList) """ self.log.info("Solving astrometry") if bbox is None: bbox = exposure.getBBox() if not self.solver: self.makeSubtask("solver") astrom = self.solver.determineWcs(sourceCat=sourceCat, exposure=exposure, bbox=bbox) if astrom is None or astrom.getWcs() is None: raise RuntimeError("Unable to solve astrometry") matches = astrom.getMatches() matchMeta = astrom.getMatchMetadata() if matches is None or len(matches) == 0: raise RuntimeError("No astrometric matches") self.log.info("%d astrometric matches" % (len(matches))) # Note that this is the Wcs for the provided positions, which may be distorted exposure.setWcs(astrom.getWcs()) if self._display: frame = lsstDebug.Info(__name__).frame displayAstrometry(exposure=exposure, sourceCat=sourceCat, matches=matches, frame=frame, pause=False) return pipeBase.Struct( refCat=astrom.refCat, matches=matches, matchMeta=matchMeta, )
def measureExposure(self, exposure, positions, title="Fringe"): """Measure fringe amplitudes for an exposure The fringe amplitudes are measured as the statistic within a square aperture. The statistic within a larger aperture are subtracted so as to remove the background. @param exposure Exposure to measure @param positions Array of (x,y) for fringe measurement @param title Title for display @return Array of fringe measurements """ stats = afwMath.StatisticsControl() stats.setNumSigmaClip(self.config.stats.clip) stats.setNumIter(self.config.stats.iterations) stats.setAndMask(exposure.getMaskedImage().getMask().getPlaneBitMask( self.config.stats.badMaskPlanes)) num = self.config.num fringes = numpy.ndarray(num) for i in range(num): x, y = positions[i] small = measure(exposure.getMaskedImage(), x, y, self.config.small, self.config.stats.stat, stats) large = measure(exposure.getMaskedImage(), x, y, self.config.large, self.config.stats.stat, stats) fringes[i] = small - large import lsstDebug display = lsstDebug.Info(__name__).display if display: frame = getFrame() ds9.mtv(exposure, frame=frame, title=title) if False: with ds9.Buffering(): for x, y in positions: corners = numpy.array([[-1, -1], [1, -1], [1, 1], [-1, 1], [-1, -1]]) + [[x, y]] ds9.line(corners * self.config.small, frame=frame, ctype="green") ds9.line(corners * self.config.large, frame=frame, ctype="blue") return fringes
def process(self, dataRef): """Process focus CCD in preparation for focus measurement @param dataRef: Data reference for CCD @return Struct(sources: source measurements, ccdId: CCD number, filterName: name of filter, dims: exposure dimensions ) """ import lsstDebug display = lsstDebug.Info(__name__).display exp = self.isr.runDataRef(dataRef).exposure if display: import lsst.afw.display.ds9 as ds9 ds9.mtv(exp, title="Post-ISR", frame=1) self.installPsf.run(exposure=exp) bg, exp = measAlg.estimateBackground(exp, self.config.background, subtract=True) if display: ds9.mtv(exp, title="Post-background", frame=2) dmResults = self.detectAndMeasure.run(exp, dataRef.get("expIdInfo")) sources = dmResults.sourceCat self.starSelector.run(exp, sources, isStarField="hscPipeline_focus_candidate") if display: ds9.mtv(exp, title="Post-measurement", frame=3) with ds9.Buffering(): for s in sources: ds9.dot("o", s.getX(), s.getY(), frame=3, ctype=ds9.GREEN if s.get("calib.psf.candidate") else ds9.RED) import pdb;pdb.set_trace() # pause to allow inspection filterName = exp.getFilter().getName() if self.config.doWrite: dataRef.put(sources, "src") dataRef.put(exp, "visitim") return Struct(sources=sources, ccdId=dataRef.dataId["ccd"], filterName=filterName, dims=exp.getDimensions())
def run(self, exposure, fringes, seed=None): """Remove fringes from the provided science exposure. Primary method of FringeTask. Fringes are only subtracted if the science exposure has a filter listed in the configuration. @param exposure Science exposure from which to remove fringes @param fringes Exposure or list of Exposures @param seed 32-bit unsigned integer for random number generator """ import lsstDebug display = lsstDebug.Info(__name__).display if not self.checkFilter(exposure): return if seed is None: seed = self.config.stats.rngSeedOffset rng = numpy.random.RandomState(seed=seed) if not hasattr(fringes, '__iter__'): fringes = [fringes] mask = exposure.getMaskedImage().getMask() for fringe in fringes: fringe.getMaskedImage().getMask().__ior__(mask) if self.config.pedestal: self.removePedestal(fringe) # Placeholder implementation for multiple fringe frames # This needs to be revisited in DM-4441 positions = self.generatePositions(fringes[0], rng) fluxes = numpy.ndarray([self.config.num, len(fringes)]) for i, f in enumerate(fringes): fluxes[:, i] = self.measureExposure(f, positions, title="Fringe frame") expFringes = self.measureExposure(exposure, positions, title="Science") solution = self.solve(expFringes, fluxes) self.subtract(exposure, fringes, solution) if display: ds9.mtv(exposure, title="Fringe subtracted", frame=getFrame())
def __init__(self, *, butler=None, **kwargs): # TODO: rename psfRefObjLoader to refObjLoader super().__init__(**kwargs) self.makeSubtask("isr") self.makeSubtask("charImage", butler=butler, refObjLoader=None) self.debug = lsstDebug.Info(__name__) if self.debug.enabled: self.log.info("Running with debug enabled...") # If we're displaying, test it works and save displays for later. # It's worth testing here as displays are flaky and sometimes # can't be contacted, and given processing takes a while, # it's a shame to fail late due to display issues. if self.debug.display: try: import lsst.afw.display as afwDisp afwDisp.setDefaultBackend(self.debug.displayBackend) afwDisp.Display.delAllDisplays() # pick an unlikely number to be safe xxx replace this self.disp1 = afwDisp.Display(987, open=True) im = afwImage.ImageF(2, 2) im.array[:] = np.ones((2, 2)) self.disp1.mtv(im) self.disp1.erase() afwDisp.setDefaultMaskTransparency(90) except NameError: self.debug.display = False self.log.warn( 'Failed to setup/connect to display! Debug display has been disabled' ) if self.debug.notHeadless: pass # other backend options can go here else: # this stop windows popping up when plotting. When headless, use 'agg' backend too plt.interactive(False) self.config.validate() self.config.freeze()
def run(self, calexp): """Operate on in-memory data. Returns ------- `Struct` instance with produced result. """ _LOG.info("executing %s: calexp=%s", self.getName(), calexp) # To test lsstDebug function make a debug.py file with this contents # somewhere in PYTHONPATH and run `pipetask` with --debug option: # # import lsstDebug # lsstDebug.Info('lsst.ctrl.mpexec.examples.calexpToCoaddTask').display = True # if lsstDebug.Info(__name__).display: _LOG.info("%s: display enabled", __name__) # output data, scalar in this case data = ExposureF(100, 100) # attribute name of struct is the same as a config field name return Struct(coadd=data)