814 inputs = butlerQC.get(inputRefs)
815 exposures = inputs.pop(
"exposures")
817 exposure_record = inputRefs.exposures[0].dataId.records[
"exposure"]
818 if self.config.useButlerExposureRegion:
819 exposure_region = butlerQC.quantum.dataId.region
821 exposure_region =
None
823 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
825 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
826 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
827 refCats=inputs.pop(
"astrometry_ref_cat"),
828 name=self.config.connections.astrometry_ref_cat,
829 config=self.config.astrometry_ref_loader, log=self.log)
830 self.astrometry.setRefObjLoader(astrometry_loader)
832 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
833 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
834 refCats=inputs.pop(
"photometry_ref_cat"),
835 name=self.config.connections.photometry_ref_cat,
836 config=self.config.photometry_ref_loader, log=self.log)
837 self.photometry.match.setRefObjLoader(photometry_loader)
839 if self.config.doMaskDiffractionSpikes:
842 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
844 if self.config.do_illumination_correction:
845 background_flat = inputs.pop(
"background_flat")
846 illumination_correction = inputs.pop(
"illumination_correction")
848 background_flat =
None
849 illumination_correction =
None
852 if self.config.useButlerCamera:
853 if "camera_model" in inputs:
854 camera_model = inputs.pop(
"camera_model")
856 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
857 "astrometry fit will use the WCS already attached to the exposure.",
858 exposures[0].filter.bandLabel)
861 assert not inputs,
"runQuantum got more inputs than expected"
865 result = pipeBase.Struct(
867 stars_footprints=
None,
868 psf_stars_footprints=
None,
869 background_to_photometric_ratio=
None,
875 id_generator=id_generator,
876 background_flat=background_flat,
877 illumination_correction=illumination_correction,
878 camera_model=camera_model,
879 exposure_record=exposure_record,
880 exposure_region=exposure_region,
882 except pipeBase.AlgorithmError
as e:
883 error = pipeBase.AnnotatedPartialOutputsError.annotate(
887 result.psf_stars_footprints,
888 result.stars_footprints,
891 butlerQC.put(result, outputRefs)
894 butlerQC.put(result, outputRefs)
903 background_flat=None,
904 illumination_correction=None,
906 exposure_record=None,
907 exposure_region=None,
909 """Find stars and perform psf measurement, then do a deeper detection
910 and measurement and calibrate astrometry and photometry from that.
914 exposures : `lsst.afw.image.Exposure` or \
915 `list` [`lsst.afw.image.Exposure`]
916 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
917 Modified in-place during processing if only one is passed.
918 If two exposures are passed, treat them as snaps and combine
919 before doing further processing.
920 id_generator : `lsst.meas.base.IdGenerator`, optional
921 Object that generates source IDs and provides random seeds.
922 result : `lsst.pipe.base.Struct`, optional
923 Result struct that is modified to allow saving of partial outputs
924 for some failure conditions. If the task completes successfully,
925 this is also returned.
926 background_flat : `lsst.afw.image.Exposure`, optional
927 Background flat-field image.
928 illumination_correction : `lsst.afw.image.Exposure`, optional
929 Illumination correction image.
930 camera_model : `lsst.afw.cameraGeom.Camera`, optional
931 Camera to be used if constructing updated WCS.
932 exposure_record : `lsst.daf.butler.DimensionRecord`, optional
933 Butler metadata for the ``exposure`` dimension. Used if
934 constructing an updated WCS instead of the boresight and
935 orientation angle from the visit info.
936 exposure_region : `lsst.sphgeom.Region`, optional
937 The exposure region to use for the reference catalog filtering.
938 If `None`, this region will be set as a padded bbox + current WCS
943 result : `lsst.pipe.base.Struct`
944 Results as a struct with attributes:
947 Calibrated exposure, with pixels in nJy units.
948 (`lsst.afw.image.Exposure`)
950 Stars that were used to calibrate the exposure, with
951 calibrated fluxes and magnitudes.
952 (`astropy.table.Table`)
954 Footprints of stars that were used to calibrate the exposure.
955 (`lsst.afw.table.SourceCatalog`)
957 Stars that were used to determine the image PSF.
958 (`astropy.table.Table`)
959 ``psf_stars_footprints``
960 Footprints of stars that were used to determine the image PSF.
961 (`lsst.afw.table.SourceCatalog`)
963 Background that was fit to the exposure when detecting
964 ``stars``. (`lsst.afw.math.BackgroundList`)
965 ``applied_photo_calib``
966 Photometric calibration that was fit to the star catalog and
967 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
968 This is `None` if ``config.do_calibrate_pixels`` is `False`.
969 ``astrometry_matches``
970 Reference catalog stars matches used in the astrometric fit.
971 (`list` [`lsst.afw.table.ReferenceMatch`] or
972 `lsst.afw.table.BaseCatalog`).
973 ``photometry_matches``
974 Reference catalog stars matches used in the photometric fit.
975 (`list` [`lsst.afw.table.ReferenceMatch`] or
976 `lsst.afw.table.BaseCatalog`).
978 Copy of the mask plane of `exposure`.
979 (`lsst.afw.image.Mask`)
982 result = pipeBase.Struct()
983 if id_generator
is None:
984 id_generator = lsst.meas.base.IdGenerator()
986 result.exposure = self.snap_combine.run(exposures).exposure
987 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
989 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
992 if self.config.do_illumination_correction:
993 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
994 raise pipeBase.InvalidQuantumError(
995 "Cannot use do_illumination_correction with an image that has not had a flat applied",
1004 result.exposure, camera_model, exposure_record=exposure_record
1006 elif exposure_record
is not None:
1009 result.background =
None
1010 result.background_to_photometric_ratio =
None
1011 summary_stat_catalog =
None
1016 have_fit_psf =
False
1017 have_fit_astrometry =
False
1018 have_fit_photometry =
False
1023 illumination_correction,
1026 result.psf_stars_footprints, _, adaptive_det_res_struct = self.
_compute_psf(
1035 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
1036 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
1038 n_sources=len(result.psf_stars_footprints),
1039 psf_shape_ixx=psf_shape.getIxx(),
1040 psf_shape_iyy=psf_shape.getIyy(),
1041 psf_shape_ixy=psf_shape.getIxy(),
1042 psf_size=psf_shape.getDeterminantRadius(),
1045 if result.background
is None:
1046 result.background = afwMath.BackgroundList()
1049 result.psf_stars = result.psf_stars_footprints.asAstropy()
1052 afwTable.updateSourceCoords(
1053 result.exposure.wcs,
1054 sourceList=result.psf_stars_footprints,
1055 include_covariance=self.config.do_include_astrometric_errors,
1058 result.exposure, result.psf_stars_footprints, exposure_region=exposure_region
1060 num_astrometry_matches = len(astrometry_matches)
1061 if "astrometry_matches" in self.config.optional_outputs:
1064 result.psf_stars = result.psf_stars_footprints.asAstropy()
1068 if self.config.doMaskDiffractionSpikes:
1069 self.diffractionSpikeMask.run(result.exposure)
1071 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
1072 if self.config.do_remeasure_star_background
and self.config.do_adaptive_threshold_detection:
1075 background_to_photometric_ratio=result.background_to_photometric_ratio,
1083 background_to_photometric_ratio=result.background_to_photometric_ratio,
1084 adaptive_det_res_struct=adaptive_det_res_struct,
1085 num_astrometry_matches=num_astrometry_matches,
1087 psf = result.exposure.getPsf()
1088 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1089 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1093 afwTable.updateSourceCoords(
1094 result.exposure.wcs,
1095 sourceList=result.stars_footprints,
1096 include_covariance=self.config.do_include_astrometric_errors,
1099 summary_stat_catalog = result.stars_footprints
1100 result.stars = result.stars_footprints.asAstropy()
1101 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1106 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1107 result.stars = result.stars_footprints.asAstropy()
1108 have_fit_astrometry =
True
1110 result.stars_footprints, photometry_matches, \
1111 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1112 have_fit_photometry =
True
1113 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1116 result.stars = result.stars_footprints.asAstropy()
1120 summary_stat_catalog = result.stars_footprints
1121 if "photometry_matches" in self.config.optional_outputs:
1124 if "mask" in self.config.optional_outputs:
1125 result.mask = result.exposure.mask.clone()
1126 except pipeBase.AlgorithmError:
1127 if not have_fit_psf:
1128 result.exposure.setPsf(
None)
1129 if not have_fit_astrometry:
1130 result.exposure.setWcs(
None)
1131 if not have_fit_photometry:
1132 result.exposure.setPhotoCalib(
None)
1139 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1142 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1147 bgIgnoredPixelMasks = lsst.meas.algorithms.SubtractBackgroundConfig().ignoredPixelMask.list()
1148 for maskName
in self.config.background_stats_ignored_pixel_masks:
1149 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1150 and maskName
not in bgIgnoredPixelMasks):
1151 bgIgnoredPixelMasks += [maskName]
1153 statsCtrl = afwMath.StatisticsControl()
1154 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(bgIgnoredPixelMasks))
1156 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.MEDIAN, statsCtrl)
1157 median_bg, _ = statObj.getResult(afwMath.MEDIAN)
1158 self.metadata[
"bg_subtracted_skyPixel_instFlux_median"] = median_bg
1160 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.STDEV, statsCtrl)
1161 stdev_bg, _ = statObj.getResult(afwMath.STDEV)
1162 self.metadata[
"bg_subtracted_skyPixel_instFlux_stdev"] = stdev_bg
1164 self.metadata[
"bg_subtracted_skySource_flux_median"] = (
1165 np.median(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1167 self.metadata[
"bg_subtracted_skySource_flux_stdev"] = (
1168 np.std(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1171 if self.config.do_calibrate_pixels:
1175 background_to_photometric_ratio=result.background_to_photometric_ratio,
1177 result.applied_photo_calib = photo_calib
1179 result.applied_photo_calib =
None
1183 if self.config.run_sattle:
1187 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1188 historical=self.config.sattle_historical)
1189 self.log.info(
'Successfully triggered load of sattle visit cache')
1190 except requests.exceptions.HTTPError:
1191 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1241 """Find bright sources detected on an exposure and fit a PSF model to
1242 them, repairing likely cosmic rays before detection.
1244 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1245 model does not include contributions from cosmic rays.
1249 result : `lsst.pipe.base.Struct`
1250 Result struct that is modified to allow saving of partial outputs
1251 for some failure conditions. Should contain at least the following
1254 - exposure : `lsst.afw.image.Exposure`
1255 Exposure to detect and measure bright stars on.
1256 - background : `lsst.afw.math.BackgroundList` | `None`
1257 Background that was fit to the exposure during detection.
1258 - background_to_photometric_ratio : `lsst.afw.image.Image` | `None`
1259 Image to convert photometric-flattened image to
1260 background-flattened image.
1261 id_generator : `lsst.meas.base.IdGenerator`
1262 Object that generates source IDs and provides random seeds.
1266 sources : `lsst.afw.table.SourceCatalog`
1267 Catalog of detected bright sources.
1268 cell_set : `lsst.afw.math.SpatialCellSet`
1269 PSF candidates returned by the psf determiner.
1270 adaptive_det_res_struct : `lsst.pipe.base.Struct`
1271 Result struct from the adaptive threshold detection.
1275 This method modifies the exposure, background and
1276 background_to_photometric_ratio attributes of the result struct
1279 def log_psf(msg, addToMetadata=False):
1280 """Log the parameters of the psf and background, with a prepended
1281 message. There is also the option to add the PSF sigma to the task
1287 Message to prepend the log info with.
1288 addToMetadata : `bool`, optional
1289 Whether to add the final psf sigma value to the task
1290 metadata (the default is False).
1292 position = result.exposure.psf.getAveragePosition()
1293 sigma = result.exposure.psf.computeShape(position).getDeterminantRadius()
1294 dimensions = result.exposure.psf.computeImage(position).getDimensions()
1295 if result.background
is not None:
1296 median_background = np.median(result.background.getImage().array)
1298 median_background = 0.0
1299 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1300 msg, sigma, dimensions, median_background)
1302 self.metadata[
"final_psf_sigma"] = sigma
1304 self.log.info(
"First pass detection with Gaussian PSF FWHM=%s pixels",
1305 self.config.install_simple_psf.fwhm)
1306 self.install_simple_psf.run(exposure=result.exposure)
1308 result.background = self.psf_subtract_background.run(
1309 exposure=result.exposure,
1310 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1312 log_psf(
"Initial PSF:")
1313 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1315 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1316 if not self.config.do_adaptive_threshold_detection:
1317 adaptive_det_res_struct =
None
1320 detections = self.psf_detection.run(
1322 exposure=result.exposure,
1323 background=result.background,
1324 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1327 initialThreshold = self.config.psf_detection.thresholdValue
1328 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1329 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1330 table, result.exposure,
1331 initialThreshold=initialThreshold,
1332 initialThresholdMultiplier=initialThresholdMultiplier,
1334 detections = adaptive_det_res_struct.detections
1336 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1337 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1338 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1339 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1340 self.psf_source_measurement.run(detections.sources, result.exposure)
1341 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1348 if not self.config.do_adaptive_threshold_detection:
1352 self.install_simple_psf.run(exposure=result.exposure)
1354 log_psf(
"Rerunning with simple PSF:")
1362 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1365 detections = self.psf_detection.run(
1367 exposure=result.exposure,
1368 background=result.background,
1369 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1371 self.psf_source_measurement.run(detections.sources, result.exposure)
1372 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1373 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1374 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1375 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1376 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1377 log_psf(
"Final PSF:", addToMetadata=
True)
1380 self.psf_repair.run(exposure=result.exposure)
1382 self.psf_source_measurement.run(detections.sources, result.exposure)
1386 return detections.sources, psf_result.cellSet, adaptive_det_res_struct
1418 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1419 adaptive_det_res_struct=None, num_astrometry_matches=None):
1420 """Detect stars on an exposure that has a PSF model, and measure their
1421 PSF, circular aperture, compensated gaussian fluxes.
1425 exposure : `lsst.afw.image.Exposure`
1426 Exposure to detect and measure stars on.
1427 background : `lsst.afw.math.BackgroundList`
1428 Background that was fit to the exposure during detection;
1429 modified in-place during subsequent detection.
1430 id_generator : `lsst.meas.base.IdGenerator`
1431 Object that generates source IDs and provides random seeds.
1432 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1433 Image to convert photometric-flattened image to
1434 background-flattened image.
1438 stars : `SourceCatalog`
1439 Sources that are very likely to be stars, with a limited set of
1440 measurements performed on them.
1443 id_generator.make_table_id_factory())
1445 maxAdaptiveDetIter = 8
1447 if adaptive_det_res_struct
is not None:
1448 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1449 adaptiveDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1450 if self.config.do_remeasure_star_background:
1451 adaptiveDetectionConfig.reEstimateBackground =
False
1453 adaptiveDetectionConfig.reEstimateBackground =
True
1454 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1456 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1458 adaptiveDetectionConfig.thresholdValue = max(
1459 self.config.star_detection.thresholdValue,
1460 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1462 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1463 "thresholdValue = %.2f and multiplier = %.1f",
1464 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1465 adaptiveDetectionConfig.includeThresholdMultiplier)
1466 adaptiveDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1467 config=adaptiveDetectionConfig
1469 detections = adaptiveDetectionTask.run(
1472 background=background,
1473 backgroundToPhotometricRatio=background_to_photometric_ratio,
1475 nFootprint = len(detections.sources)
1478 for src
in detections.sources:
1479 nPeakSrc = len(src.getFootprint().getPeaks())
1483 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1485 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1486 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1487 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1488 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1489 threshFactor = max(0.01, 1.5*threshFactor)
1490 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1491 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1495 threshFactor *= 0.75
1496 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1497 "factor to %.2f. and rerunning.", threshFactor)
1501 detections = self.star_detection.run(
1504 background=background,
1505 backgroundToPhotometricRatio=background_to_photometric_ratio,
1507 sources = detections.sources
1508 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1510 n_sky_sources = np.sum(sources[
"sky_source"])
1511 if (self.config.do_downsample_footprints
1512 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1513 if exposure.info.id
is None:
1514 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1517 seed = exposure.info.id & 0xFFFFFFFF
1519 gen = np.random.RandomState(seed)
1522 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1523 indices = gen.choice(
1525 size=self.config.downsample_max_footprints,
1528 skyIndices, = np.where(sources[
"sky_source"])
1529 indices = np.concatenate((indices, skyIndices))
1531 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1533 sel = np.zeros(len(sources), dtype=bool)
1535 sources = sources[sel]
1539 self.star_deblend.run(exposure=exposure, sources=sources)
1542 if not sources.isContiguous():
1543 sources = sources.copy(deep=
True)
1546 self.star_measurement.run(sources, exposure)
1547 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1548 self.metadata[
"failed_deblend_source_count"] = np.sum(
1549 ~sources[
"sky_source"] & sources[
"deblend_failed"]
1551 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1552 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1557 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1558 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1559 self.star_catalog_calculation.run(sources)
1560 self.star_set_primary_flags.run(sources)
1562 result = self.star_selector.run(sources)
1564 if not result.sourceCat.isContiguous():
1565 return result.sourceCat.copy(deep=
True)
1567 return result.sourceCat
1946 """Remeasure the exposure's background with iterative adaptive
1947 threshold detection.
1951 result : `lsst.pipe.base.Struct`
1952 The current state of the result Strut from the run method of
1953 CalibrateImageTask. Will be modified in place.
1954 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1955 Image to convert photometric-flattened image to
1956 background-flattened image.
1960 result : `lsst.pipe.base.Struct`
1961 The modified result Struct with the new background subtracted.
1966 backgroundOrig = result.background.clone()
1967 median_background_orig = np.median(backgroundOrig.getImage().array)
1968 self.log.info(
"Original median_background = %.2f", median_background_orig)
1969 result.exposure.image.array += result.background.getImage().array
1970 result.background = afwMath.BackgroundList()
1972 origMask = result.exposure.mask.clone()
1973 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
1974 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
1976 detected_mask_planes,
1978 minDetFracForFinalBg = 0.02
1979 maxDetFracForFinalBg = 0.93
1983 maxAdaptiveDetIter = 10
1984 for dilateIter
in range(maxAdaptiveDetIter):
1985 dilatedMask = origMask.clone()
1986 for maskName
in detected_mask_planes:
1988 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1989 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1990 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1991 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1993 detectedMask = dilatedMask.getMaskPlane(maskName)
1994 dilatedMask.clearMaskPlane(detectedMask)
1996 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1999 detected_mask_planes,
2001 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
2006 result.exposure.mask = dilatedMask
2007 self.log.debug(
"detected_fraction_orig = %.3f; detected_fraction_dilated = %.3f",
2008 detected_fraction_orig, detected_fraction_dilated)
2009 n_above_max_per_amp = -99
2010 highest_detected_fraction_per_amp = float(
"nan")
2013 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2015 detected_mask_planes, bad_mask_planes)
2016 self.log.debug(
"Dilated mask: n_above_max_per_amp = %d; "
2017 "highest_detected_fraction_per_amp = %.3f",
2018 n_above_max_per_amp, highest_detected_fraction_per_amp)
2020 bgIgnoreMasksToAdd = [
"SAT",
"SUSPECT",
"SPIKE"]
2021 detected_fraction = 1.0
2022 nFootprintTemp = 1e12
2023 starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
2024 for maskName
in bgIgnoreMasksToAdd:
2025 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2026 and maskName
not in starBackgroundDetectionConfig.background.ignoredPixelMask):
2027 starBackgroundDetectionConfig.background.ignoredPixelMask += [maskName]
2028 starBackgroundDetectionConfig.doTempLocalBackground =
False
2029 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
2030 starBackgroundDetectionConfig.reEstimateBackground =
False
2031 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
2032 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background_orig)
2033 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
2035 n_above_max_per_amp = -99
2036 highest_detected_fraction_per_amp = float(
"nan")
2037 doCheckPerAmpDetFraction =
True
2039 minFootprints = self.config.star_background_min_footprints
2041 for nIter
in range(maxIter):
2042 currentThresh = starBackgroundDetectionConfig.thresholdValue
2043 nZeroEncountered = 0
2044 if nFootprintTemp == 0:
2045 zeroFactor = min(0.98, 0.9 + 0.01*nZeroEncountered)
2046 starBackgroundDetectionConfig.thresholdValue = zeroFactor*currentThresh
2047 self.log.warning(
"No footprints detected. Decreasing threshold to %.2f and rerunning.",
2048 starBackgroundDetectionConfig.thresholdValue)
2049 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2050 config=starBackgroundDetectionConfig)
2052 tempDetections = starBackgroundDetectionTask.run(
2053 table=table, exposure=result.exposure, clearMask=
True)
2054 nFootprintTemp = len(tempDetections.sources)
2055 minFootprints = max(self.config.star_background_min_footprints,
2056 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2057 minFootprints = min(200, minFootprints)
2058 nZeroEncountered += 1
2059 if nFootprintTemp >= minFootprints:
2061 detected_mask_planes,
2063 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2064 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2065 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
2066 nIter, starBackgroundDetectionConfig.thresholdValue,
2067 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2068 nFootprintTemp, minFootprints)
2069 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2075 if nFootprintTemp > 0
and nFootprintTemp < minFootprints:
2078 if detected_fraction > maxDetFracForFinalBg
or nFootprintTemp <= minFootprints:
2079 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
2080 if nFootprintTemp < minFootprints
and detected_fraction > 0.9*maxDetFracForFinalBg:
2081 if nFootprintTemp == 1:
2082 starBackgroundDetectionConfig.thresholdValue = 1.4*currentThresh
2084 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
2086 if n_above_max_per_amp > 1:
2087 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
2088 if detected_fraction < minDetFracForFinalBg:
2089 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
2090 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2091 config=starBackgroundDetectionConfig)
2093 tempDetections = starBackgroundDetectionTask.run(
2094 table=table, exposure=result.exposure, clearMask=
True)
2095 result.exposure.mask |= dilatedMask
2096 nFootprintTemp = len(tempDetections.sources)
2097 minFootprints = max(self.config.star_background_min_footprints,
2098 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2099 minFootprints = min(200, minFootprints)
2102 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2103 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2104 "(max is %.3f; min is %.3f); nFootprint = %d (current min is %d)",
2105 nIter, starBackgroundDetectionConfig.thresholdValue,
2106 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2107 nFootprintTemp, minFootprints)
2108 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2110 n_amp = len(result.exposure.detector.getAmplifiers())
2111 if doCheckPerAmpDetFraction:
2112 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2114 detected_mask_planes, bad_mask_planes)
2116 if not no_zero_det_amps:
2117 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
2119 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
2120 and n_above_max_per_amp < int(0.75*n_amp)
2121 and no_zero_det_amps
2122 and nFootprintTemp >= minFootprints):
2123 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
2124 or detected_fraction < 0.85*maxDetFracForFinalBg):
2127 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
2128 self.log.debug(
"Number of amplifiers with detected fraction above the maximum "
2129 "(%.2f) excedes the maximum allowed (%d >= %d). Making a small "
2130 "tweak to the threshold (from %.2f to %.2f) and rerunning.",
2131 maxDetFracForFinalBg, n_above_max_per_amp, int(0.75*n_amp),
2132 currentThresh, starBackgroundDetectionConfig.thresholdValue)
2133 self.log.debug(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
2137 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
2138 "(highest per amp section = %.5f)",
2139 detected_fraction, highest_detected_fraction_per_amp)
2141 if detected_fraction > maxDetFracForFinalBg:
2142 result.exposure.mask = dilatedMask
2143 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
2144 "was too large in star_background_detection = %.3f (max = %.3f). "
2145 "Reverting to dilated mask from PSF detection.",
2146 detected_fraction, maxDetFracForFinalBg)
2147 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
2148 star_background = self.star_background.run(
2149 exposure=result.exposure,
2150 backgroundToPhotometricRatio=background_to_photometric_ratio,
2152 result.background.append(star_background[0])
2154 median_background = np.median(result.background.getImage().array)
2155 self.log.info(
"New initial median_background = %.2f", median_background)
2163 if detected_fraction < 0.5:
2164 dilatedMask = result.exposure.mask.clone()
2165 for maskName
in detected_mask_planes:
2167 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2168 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2169 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2170 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2172 detectedMask = dilatedMask.getMaskPlane(maskName)
2173 dilatedMask.clearMaskPlane(detectedMask)
2175 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2178 detected_mask_planes,
2180 result.exposure.mask = dilatedMask
2181 self.log.debug(
"detected_fraction_orig = %.3f; detected_fraction_dilated = %.3f",
2182 detected_fraction_orig, detected_fraction_dilated)
2184 bbox = result.exposure.getBBox()
2195 pedestalBackgroundConfig = lsst.meas.algorithms.SubtractBackgroundConfig()
2196 for maskName
in bgIgnoreMasksToAdd:
2197 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2198 and maskName
not in pedestalBackgroundConfig.ignoredPixelMask):
2199 pedestalBackgroundConfig.ignoredPixelMask += [maskName]
2200 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
2201 pedestalBackgroundConfig.approxOrderX = 0
2202 pedestalBackgroundConfig.binSize = min(32, int(math.ceil(max(bbox.width, bbox.height)/4.0)))
2203 self.log.info(
"Initial pedestal binSize = %d pixels", pedestalBackgroundConfig.binSize)
2204 cumulativePedestalLevel = 0.0
2207 relativeStoppingCriterion = 0.05
2211 absoluteStoppingCriterion = 0.5
2212 inPedestalIteration =
True
2213 while inPedestalIteration:
2214 cumulativePedestalPrev = cumulativePedestalLevel
2215 pedestalBackgroundTask = lsst.meas.algorithms.SubtractBackgroundTask(
2216 config=pedestalBackgroundConfig)
2217 pedestalBackgroundList = pedestalBackgroundTask.run(
2218 exposure=result.exposure,
2219 background=result.background,
2220 backgroundToPhotometricRatio=background_to_photometric_ratio,
2224 pedestalBackground = afwMath.BackgroundList()
2225 pedestalBackground.append(pedestalBackgroundList[-1])
2226 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
2227 cumulativePedestalLevel += pedestalBackgroundLevel
2228 absoluteDelta = abs(cumulativePedestalLevel - cumulativePedestalPrev)
2229 if cumulativePedestalPrev != 0.0:
2230 relativeDelta = abs(absoluteDelta/cumulativePedestalPrev)
2233 self.log.info(
"Subtracted pedestalBackgroundLevel = %.4f (cumulative = %.4f; "
2234 "relativeDelta = %.4f; absoluteDelta = %.3f)",
2235 pedestalBackgroundLevel, cumulativePedestalLevel,
2236 relativeDelta, absoluteDelta)
2238 if (relativeDelta < relativeStoppingCriterion
2239 or absoluteDelta < absoluteStoppingCriterion):
2241 inPedestalIteration =
False
2244 if pedestalBackgroundConfig.binSize*2 < 2*max(bbox.width, bbox.height):
2245 pedestalBackgroundConfig.binSize = int(pedestalBackgroundConfig.binSize*2)
2246 self.log.info(
"Increasing pedestal binSize to %d pixels and remeasuring.",
2247 pedestalBackgroundConfig.binSize)
2250 self.log.warning(
"Reached maximum bin size. Exiting pedestal loop without meeting "
2251 "the convergence criteria (relativeDelta <= %.2f or "
2252 "absoluteDelta <= %.2f).",
2253 relativeStoppingCriterion, absoluteStoppingCriterion)
2254 inPedestalIteration =
False
2255 self.log.info(
"Final subtracted cumulativePedestalBackgroundLevel = %.4f", cumulativePedestalLevel)
2258 mask = result.exposure.mask
2259 for mp
in detected_mask_planes:
2260 if mp
not in mask.getMaskPlaneDict():
2261 mask.addMaskPlane(mp)
2262 mask &= ~mask.getPlaneBitMask(detected_mask_planes)