def run(self, exposure): table = SourceTable.make(self.schema) background = None detRes = self.detection.run(table=table, exposure=exposure) sourceCat = detRes.sources if background is None: background = BackgroundList() if detRes.fpSets.background: background.append(detRes.fpSets.background) if self.config.doDeblend: self.deblend.run(exposure=exposure, sources=sourceCat) self.measurement.run( measCat=sourceCat, exposure=exposure ) #results2 = self.deblend.run(...) #results3 = self.measurement.run(...) return pipeBase.Struct( exposure = exposure, background = background, sourceCat = sourceCat )
def run(self, exposure, exposureIdInfo, background=None, allowApCorr=True): """!Detect, deblend and perform single-frame measurement on sources and refine the background model @param[in,out] exposure exposure to process. Background must already be subtracted to reasonable accuracy, else detection will fail. The background model is refined and resubtracted. apCorrMap is set if measuring aperture correction. @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo) @param[in,out] background background model to be modified (an lsst.afw.math.BackgroundList), or None to create a new background model @param[in] allowApCorr allow measuring and applying aperture correction? @return pipe_base Struct containing these fields: - exposure: input exposure (as modified in the course of runing) - sourceCat: source catalog (an lsst.afw.table.SourceTable) - background: background model (input as modified, or a new model if input is None); an lsst.afw.math.BackgroundList """ if background is None: background = BackgroundList() sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: background.append(detRes.fpSets.background) if self.config.doDeblend: self.deblend.run( exposure=exposure, sources=sourceCat, psf=exposure.getPsf(), ) self.measure( exposure=exposure, exposureIdInfo=exposureIdInfo, sourceCat=sourceCat, allowApCorr=allowApCorr, ) return pipeBase.Struct( exposure=exposure, sourceCat=sourceCat, background=background, )
def run(self, exposure, exposureIdInfo, background=None, allowApCorr=True): """!Detect, deblend and perform single-frame measurement on sources and refine the background model @param[in,out] exposure exposure to process. Background must already be subtracted to reasonable accuracy, else detection will fail. The background model is refined and resubtracted. apCorrMap is set if measuring aperture correction. @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo) @param[in,out] background background model to be modified (an lsst.afw.math.BackgroundList), or None to create a new background model @param[in] allowApCorr allow measuring and applying aperture correction? @return pipe_base Struct containing these fields: - exposure: input exposure (as modified in the course of runing) - sourceCat: source catalog (an lsst.afw.table.SourceTable) - background: background model (input as modified, or a new model if input is None); an lsst.afw.math.BackgroundList """ if background is None: background = BackgroundList() sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: background.append(detRes.fpSets.background) if self.config.doDeblend: self.deblend.run( exposure = exposure, sources = sourceCat, psf = exposure.getPsf(), ) self.measure( exposure = exposure, exposureIdInfo = exposureIdInfo, sourceCat = sourceCat, allowApCorr = allowApCorr, ) return pipeBase.Struct( exposure = exposure, sourceCat = sourceCat, background = background, )
def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background): """!Perform one iteration of detect, measure and estimate PSF Performs the following operations: - if config.doMeasurePsf or not exposure.hasPsf(): - install a simple PSF model (replacing the existing one, if need be) - interpolate over cosmic rays with keepCRs=True - estimate background and subtract it from the exposure - detect, deblend and measure sources, and subtract a refined background model; - if config.doMeasurePsf: - measure PSF @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar) The following changes are made: - update or set psf - update detection and cosmic ray mask planes - subtract background @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo) @param[in,out] background initial model of background already subtracted from exposure (an lsst.afw.math.BackgroundList). @return pipe_base Struct containing these fields, all from the final iteration of detect sources, measure sources and estimate PSF: - exposure characterized exposure; image is repaired by interpolating over cosmic rays, mask is updated accordingly, and the PSF model is set - sourceCat detected sources (an lsst.afw.table.SourceCatalog) - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) """ # install a simple PSF model, if needed or wanted if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf): self.log.warn( "Source catalog detected and measured with placeholder or default PSF" ) self.installSimplePsf.run(exposure=exposure) # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model) self.repair.run(exposure=exposure, keepCRs=True) self.display("repair_iter", exposure=exposure) if background is None: background = BackgroundList() sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: for bg in detRes.fpSets.background: background.append(bg) if self.config.doDeblend: self.deblend.run(exposure=exposure, sources=sourceCat) self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId) measPsfRes = pipeBase.Struct(cellSet=None) if self.config.doMeasurePsf: if self.measurePsf.usesMatches: matches = self.ref_match.loadAndMatch( exposure=exposure, sourceCat=sourceCat).matches else: matches = None measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches, expId=exposureIdInfo.expId) self.display("measure_iter", exposure=exposure, sourceCat=sourceCat) return pipeBase.Struct( exposure=exposure, sourceCat=sourceCat, background=background, psfCellSet=measPsfRes.cellSet, )
def characterize(self, exposure, exposureIdInfo, background=None): """!Characterize a science image Peforms the following operations: - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false: - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details) - interpolate over cosmic rays - perform final measurement @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). The following changes are made: - update or set psf - set apCorrMap - update detection and cosmic ray mask planes - subtract background and interpolate over cosmic rays @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo) @param[in] background model of background model already subtracted from exposure (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, which is typical for image characterization. @return pipe_base Struct containing these fields, all from the final iteration of detectMeasureAndEstimatePsf: - exposure: characterized exposure; image is repaired by interpolating over cosmic rays, mask is updated accordingly, and the PSF model is set - sourceCat: detected sources (an lsst.afw.table.SourceCatalog) - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList) - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) """ self._frame = self._initialFrame # reset debug display frame if not self.config.doMeasurePsf and not exposure.hasPsf(): raise RuntimeError("exposure has no PSF model and config.doMeasurePsf false") if background is None: background = BackgroundList() # make a deep copy of the mask originalMask = exposure.getMaskedImage().getMask().clone() # subtract an initial estimate of background level estBg = estimateBackground( exposure = exposure, backgroundConfig = self.config.background, subtract = False, # this makes a deep copy, which we don't want )[0] image = exposure.getMaskedImage().getImage() image -= estBg.getImageF() background.append(estBg) psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1 for i in range(psfIterations): if i > 1: # restore original mask so that detections and cosmic rays # are only marked by the final iteration exposure.getMaskedImage().getMask()[:] = originalMask dmeRes = self.detectMeasureAndEstimatePsf( exposure = exposure, exposureIdInfo = exposureIdInfo, background = background, ) psf = dmeRes.exposure.getPsf() psfSigma = psf.computeShape().getDeterminantRadius() psfDimensions = psf.computeImage().getDimensions() medBackground = np.median(dmeRes.background.getImage().getArray()) self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" % \ (i + 1, psfSigma, psfDimensions, medBackground)) self.display("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) # perform final repair with final PSF self.repair.run(exposure=dmeRes.exposure) self.display("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) # perform final measurement with final PSF, including measuring and applying aperture correction, # if wanted self.detectAndMeasure.measure( exposure = dmeRes.exposure, exposureIdInfo = exposureIdInfo, sourceCat = dmeRes.sourceCat, allowApCorr = True, # the default; listed for emphasis ) self.display("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) return dmeRes
def run(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None): """!Calibrate an exposure (science image or coadd) @param[in,out] exposure exposure to calibrate (an lsst.afw.image.ExposureF or similar); in: - MaskedImage - Psf out: - MaskedImage has background subtracted - Wcs is replaced - PhotoCalib is replaced @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo) If not provided, returned SourceCatalog IDs will not be globally unique. @param[in,out] background background model already subtracted from exposure (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, though that is unusual for calibration. A refined background model is output. @param[in] icSourceCat A SourceCatalog from CharacterizeImageTask from which we can copy some fields. @return pipe_base Struct containing these fields: - exposure calibrate science exposure with refined WCS and PhotoCalib - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) - sourceCat catalog of measured sources - astromMatches list of source/refObj matches from the astrometry solver """ # detect, deblend and measure sources if exposureIdInfo is None: exposureIdInfo = ExposureIdInfo() if background is None: background = BackgroundList() sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: for bg in detRes.fpSets.background: background.append(bg) if self.config.doSkySources: skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=exposureIdInfo.expId) if skySourceFootprints: for foot in skySourceFootprints: s = sourceCat.addNew() s.setFootprint(foot) s.set(self.skySourceKey, True) if self.config.doDeblend: self.deblend.run(exposure=exposure, sources=sourceCat) self.measurement.run( measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId ) if self.config.doApCorr: self.applyApCorr.run( catalog=sourceCat, apCorrMap=exposure.getInfo().getApCorrMap() ) self.catalogCalculation.run(sourceCat) self.setPrimaryFlags.run(sourceCat, includeDeblend=self.config.doDeblend) if icSourceCat is not None and \ len(self.config.icSourceFieldsToCopy) > 0: self.copyIcSourceFields(icSourceCat=icSourceCat, sourceCat=sourceCat) # TODO DM-11568: this contiguous check-and-copy could go away if we # reserve enough space during SourceDetection and/or SourceDeblend. # NOTE: sourceSelectors require contiguous catalogs, so ensure # contiguity now, so views are preserved from here on. if not sourceCat.isContiguous(): sourceCat = sourceCat.copy(deep=True) # perform astrometry calibration: # fit an improved WCS and update the exposure's WCS in place astromMatches = None matchMeta = None if self.config.doAstrometry: try: astromRes = self.astrometry.run( exposure=exposure, sourceCat=sourceCat, ) astromMatches = astromRes.matches matchMeta = astromRes.matchMeta except Exception as e: if self.config.requireAstrometry: raise self.log.warn("Unable to perform astrometric calibration " "(%s): attempting to proceed" % e) # compute photometric calibration if self.config.doPhotoCal: try: photoRes = self.photoCal.run(exposure, sourceCat=sourceCat, expId=exposureIdInfo.expId) exposure.setPhotoCalib(photoRes.photoCalib) # TODO: reword this to phrase it in terms of the calibration factor? self.log.info("Photometric zero-point: %f" % photoRes.photoCalib.instFluxToMagnitude(1.0)) self.setMetadata(exposure=exposure, photoRes=photoRes) except Exception as e: if self.config.requirePhotoCal: raise self.log.warn("Unable to perform photometric calibration " "(%s): attempting to proceed" % e) self.setMetadata(exposure=exposure, photoRes=None) if self.config.doInsertFakes: self.insertFakes.run(exposure, background=background) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: for bg in detRes.fpSets.background: background.append(bg) if self.config.doDeblend: self.deblend.run(exposure=exposure, sources=sourceCat) self.measurement.run( measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId ) if self.config.doApCorr: self.applyApCorr.run( catalog=sourceCat, apCorrMap=exposure.getInfo().getApCorrMap() ) self.catalogCalculation.run(sourceCat) if icSourceCat is not None and len(self.config.icSourceFieldsToCopy) > 0: self.copyIcSourceFields(icSourceCat=icSourceCat, sourceCat=sourceCat) frame = getDebugFrame(self._display, "calibrate") if frame: displayAstrometry( sourceCat=sourceCat, exposure=exposure, matches=astromMatches, frame=frame, pause=False, ) return pipeBase.Struct( exposure=exposure, background=background, sourceCat=sourceCat, astromMatches=astromMatches, matchMeta=matchMeta, # These are duplicate entries with different names for use with # gen3 middleware outputExposure=exposure, outputCat=sourceCat, outputBackground=background, )
def calibrate(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None): """!Calibrate an exposure (science image or coadd) @param[in,out] exposure exposure to calibrate (an lsst.afw.image.ExposureF or similar); in: - MaskedImage - Psf out: - MaskedImage has background subtracted - Wcs is replaced - Calib zero-point is set @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo) If not provided, returned SourceCatalog IDs will not be globally unique. @param[in,out] background background model already subtracted from exposure (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, though that is unusual for calibration. A refined background model is output. @param[in] icSourceCat A SourceCatalog from CharacterizeImageTask from which we can copy some fields. @return pipe_base Struct containing these fields: - exposure calibrate science exposure with refined WCS and Calib - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) - sourceCat catalog of measured sources - astromMatches list of source/refObj matches from the astrometry solver """ # detect, deblend and measure sources if exposureIdInfo is None: exposureIdInfo = ExposureIdInfo() if background is None: background = BackgroundList() sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) if self.config.doInsertFakes: self.insertFakes.run(exposure, background=background) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: background.append(detRes.fpSets.background) if self.config.doDeblend: self.deblend.run(exposure=exposure, sources=sourceCat) self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId) if self.config.doApCorr: self.applyApCorr.run(catalog=sourceCat, apCorrMap=exposure.getInfo().getApCorrMap()) self.catalogCalculation.run(sourceCat) if icSourceCat is not None and \ len(self.config.icSourceFieldsToCopy) > 0: self.copyIcSourceFields(icSourceCat=icSourceCat, sourceCat=sourceCat) # perform astrometry calibration: # fit an improved WCS and update the exposure's WCS in place astromMatches = None if self.config.doAstrometry: try: astromRes = self.astrometry.run( exposure=exposure, sourceCat=sourceCat, ) astromMatches = astromRes.matches except Exception as e: if self.config.requireAstrometry: raise self.log.warn("Unable to perform astrometric calibration " "(%s): attempting to proceed" % e) # compute photometric calibration if self.config.doPhotoCal: try: photoRes = self.photoCal.run(exposure, sourceCat=sourceCat) exposure.getCalib().setFluxMag0(photoRes.calib.getFluxMag0()) self.log.info("Photometric zero-point: %f" % photoRes.calib.getMagnitude(1.0)) self.setMetadata(exposure=exposure, photoRes=photoRes) except Exception as e: if self.config.requirePhotoCal: raise self.log.warn("Unable to perform photometric calibration " "(%s): attempting to proceed" % e) self.setMetadata(exposure=exposure, photoRes=None) frame = getDebugFrame(self._display, "calibrate") if frame: displayAstrometry( sourceCat=sourceCat, exposure=exposure, matches=astromMatches, frame=frame, pause=False, ) return pipeBase.Struct( exposure=exposure, background=background, sourceCat=sourceCat, astromMatches=astromMatches, )
def characterize(self, exposure, exposureIdInfo, background=None): """!Characterize a science image Peforms the following operations: - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false: - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details) - interpolate over cosmic rays - perform final measurement @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). The following changes are made: - update or set psf - set apCorrMap - update detection and cosmic ray mask planes - subtract background and interpolate over cosmic rays @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo) @param[in] background model of background model already subtracted from exposure (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, which is typical for image characterization. @return pipe_base Struct containing these fields, all from the final iteration of detectMeasureAndEstimatePsf: - exposure: characterized exposure; image is repaired by interpolating over cosmic rays, mask is updated accordingly, and the PSF model is set - sourceCat: detected sources (an lsst.afw.table.SourceCatalog) - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList) - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) """ self._frame = self._initialFrame # reset debug display frame if not self.config.doMeasurePsf and not exposure.hasPsf(): raise RuntimeError( "exposure has no PSF model and config.doMeasurePsf false") if background is None: background = BackgroundList() # make a deep copy of the mask originalMask = exposure.getMaskedImage().getMask().clone() # subtract an initial estimate of background level estBg = estimateBackground( exposure=exposure, backgroundConfig=self.config.background, subtract=False, # this makes a deep copy, which we don't want )[0] image = exposure.getMaskedImage().getImage() image -= estBg.getImageF() background.append(estBg) psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1 for i in range(psfIterations): if i > 1: # restore original mask so that detections and cosmic rays # are only marked by the final iteration exposure.getMaskedImage().getMask()[:] = originalMask dmeRes = self.detectMeasureAndEstimatePsf( exposure=exposure, exposureIdInfo=exposureIdInfo, background=background, ) psf = dmeRes.exposure.getPsf() psfSigma = psf.computeShape().getDeterminantRadius() psfDimensions = psf.computeImage().getDimensions() medBackground = np.median(dmeRes.background.getImage().getArray()) self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" % \ (i + 1, psfSigma, psfDimensions, medBackground)) self.display("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) # perform final repair with final PSF self.repair.run(exposure=dmeRes.exposure) self.display("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) # perform final measurement with final PSF, including measuring and applying aperture correction, # if wanted self.detectAndMeasure.measure( exposure=dmeRes.exposure, exposureIdInfo=exposureIdInfo, sourceCat=dmeRes.sourceCat, allowApCorr=True, # the default; listed for emphasis ) self.display("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat) return dmeRes
def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background): """!Perform one iteration of detect, measure and estimate PSF Performs the following operations: - if config.doMeasurePsf or not exposure.hasPsf(): - install a simple PSF model (replacing the existing one, if need be) - interpolate over cosmic rays with keepCRs=True - estimate background and subtract it from the exposure - detect, deblend and measure sources, and subtract a refined background model; - if config.doMeasurePsf: - measure PSF @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar) The following changes are made: - update or set psf - update detection and cosmic ray mask planes - subtract background @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo) @param[in,out] background initial model of background already subtracted from exposure (an lsst.afw.math.BackgroundList). @return pipe_base Struct containing these fields, all from the final iteration of detect sources, measure sources and estimate PSF: - exposure characterized exposure; image is repaired by interpolating over cosmic rays, mask is updated accordingly, and the PSF model is set - sourceCat detected sources (an lsst.afw.table.SourceCatalog) - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) """ # install a simple PSF model, if needed or wanted if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf): self.log.warn("Source catalog detected and measured with placeholder or default PSF") self.installSimplePsf.run(exposure=exposure) # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model) self.repair.run(exposure=exposure, keepCRs=True) self.display("repair_iter", exposure=exposure) if background is None: background = BackgroundList() sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits) table = SourceTable.make(self.schema, sourceIdFactory) table.setMetadata(self.algMetadata) detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True) sourceCat = detRes.sources if detRes.fpSets.background: for bg in detRes.fpSets.background: background.append(bg) if self.config.doDeblend: self.deblend.run(exposure=exposure, sources=sourceCat) self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId) measPsfRes = pipeBase.Struct(cellSet=None) if self.config.doMeasurePsf: if self.measurePsf.usesMatches: matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches else: matches = None measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches, expId=exposureIdInfo.expId) self.display("measure_iter", exposure=exposure, sourceCat=sourceCat) return pipeBase.Struct( exposure=exposure, sourceCat=sourceCat, background=background, psfCellSet=measPsfRes.cellSet, )