829 inputs = butlerQC.get(inputRefs)
830 exposures = inputs.pop(
"exposures")
832 exposure_record = inputRefs.exposures[0].dataId.records[
"exposure"]
833 if self.config.useButlerExposureRegion:
834 exposure_region = butlerQC.quantum.dataId.region
836 exposure_region =
None
838 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
840 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
841 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
842 refCats=inputs.pop(
"astrometry_ref_cat"),
843 name=self.config.connections.astrometry_ref_cat,
844 config=self.config.astrometry_ref_loader, log=self.log)
845 self.astrometry.setRefObjLoader(astrometry_loader)
847 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
848 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
849 refCats=inputs.pop(
"photometry_ref_cat"),
850 name=self.config.connections.photometry_ref_cat,
851 config=self.config.photometry_ref_loader, log=self.log)
852 self.photometry.match.setRefObjLoader(photometry_loader)
854 if self.config.doMaskDiffractionSpikes:
857 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
859 if self.config.do_illumination_correction:
860 background_flat = inputs.pop(
"background_flat")
861 illumination_correction = inputs.pop(
"illumination_correction")
863 background_flat =
None
864 illumination_correction =
None
867 if self.config.useButlerCamera:
868 if "camera_model" in inputs:
869 camera_model = inputs.pop(
"camera_model")
871 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
872 "astrometry fit will use the WCS already attached to the exposure.",
873 exposures[0].filter.bandLabel)
876 assert not inputs,
"runQuantum got more inputs than expected"
880 result = pipeBase.Struct(
882 stars_footprints=
None,
883 psf_stars_footprints=
None,
884 background_to_photometric_ratio=
None,
890 id_generator=id_generator,
891 background_flat=background_flat,
892 illumination_correction=illumination_correction,
893 camera_model=camera_model,
894 exposure_record=exposure_record,
895 exposure_region=exposure_region,
897 except pipeBase.AlgorithmError
as e:
898 error = pipeBase.AnnotatedPartialOutputsError.annotate(
902 result.psf_stars_footprints,
903 result.stars_footprints,
906 butlerQC.put(result, outputRefs)
909 butlerQC.put(result, outputRefs)
918 background_flat=None,
919 illumination_correction=None,
921 exposure_record=None,
922 exposure_region=None,
924 """Find stars and perform psf measurement, then do a deeper detection
925 and measurement and calibrate astrometry and photometry from that.
929 exposures : `lsst.afw.image.Exposure` or \
930 `list` [`lsst.afw.image.Exposure`]
931 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
932 Modified in-place during processing if only one is passed.
933 If two exposures are passed, treat them as snaps and combine
934 before doing further processing.
935 id_generator : `lsst.meas.base.IdGenerator`, optional
936 Object that generates source IDs and provides random seeds.
937 result : `lsst.pipe.base.Struct`, optional
938 Result struct that is modified to allow saving of partial outputs
939 for some failure conditions. If the task completes successfully,
940 this is also returned.
941 background_flat : `lsst.afw.image.Exposure`, optional
942 Background flat-field image.
943 illumination_correction : `lsst.afw.image.Exposure`, optional
944 Illumination correction image.
945 camera_model : `lsst.afw.cameraGeom.Camera`, optional
946 Camera to be used if constructing updated WCS.
947 exposure_record : `lsst.daf.butler.DimensionRecord`, optional
948 Butler metadata for the ``exposure`` dimension. Used if
949 constructing an updated WCS instead of the boresight and
950 orientation angle from the visit info.
951 exposure_region : `lsst.sphgeom.Region`, optional
952 The exposure region to use for the reference catalog filtering.
953 If `None`, this region will be set as a padded bbox + current WCS
958 result : `lsst.pipe.base.Struct`
959 Results as a struct with attributes:
962 Calibrated exposure, with pixels in nJy units.
963 (`lsst.afw.image.Exposure`)
965 Stars that were used to calibrate the exposure, with
966 calibrated fluxes and magnitudes.
967 (`astropy.table.Table`)
969 Footprints of stars that were used to calibrate the exposure.
970 (`lsst.afw.table.SourceCatalog`)
972 Stars that were used to determine the image PSF.
973 (`astropy.table.Table`)
974 ``psf_stars_footprints``
975 Footprints of stars that were used to determine the image PSF.
976 (`lsst.afw.table.SourceCatalog`)
978 Background that was fit to the exposure when detecting
979 ``stars``. (`lsst.afw.math.BackgroundList`)
980 ``applied_photo_calib``
981 Photometric calibration that was fit to the star catalog and
982 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
983 This is `None` if ``config.do_calibrate_pixels`` is `False`.
984 ``astrometry_matches``
985 Reference catalog stars matches used in the astrometric fit.
986 (`list` [`lsst.afw.table.ReferenceMatch`] or
987 `lsst.afw.table.BaseCatalog`).
988 ``photometry_matches``
989 Reference catalog stars matches used in the photometric fit.
990 (`list` [`lsst.afw.table.ReferenceMatch`] or
991 `lsst.afw.table.BaseCatalog`).
993 Copy of the mask plane of `exposure`.
994 (`lsst.afw.image.Mask`)
997 result = pipeBase.Struct()
998 if id_generator
is None:
999 id_generator = lsst.meas.base.IdGenerator()
1001 result.exposure = self.snap_combine.run(exposures).exposure
1002 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
1004 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
1007 if self.config.do_illumination_correction:
1008 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
1009 raise pipeBase.InvalidQuantumError(
1010 "Cannot use do_illumination_correction with an image that has not had a flat applied",
1019 result.exposure, camera_model, exposure_record=exposure_record
1021 elif exposure_record
is not None:
1024 result.background =
None
1025 result.background_to_photometric_ratio =
None
1026 summary_stat_catalog =
None
1031 have_fit_psf =
False
1032 have_fit_astrometry =
False
1033 have_fit_photometry =
False
1038 illumination_correction,
1044 result.psf_stars_footprints = compute_psf_struct.detections_sources
1045 adaptive_det_res_struct = compute_psf_struct.adaptive_det_res_struct
1049 if self.config.do_adaptive_threshold_detection:
1050 metadata_name =
"psf_adaptive_threshold_value"
1051 result.exposure.metadata[metadata_name.upper()] = adaptive_det_res_struct.thresholdValue
1052 self.metadata[metadata_name] = adaptive_det_res_struct.thresholdValue
1053 metadata_name =
"psf_adaptive_include_threshold_multiplier"
1054 result.exposure.metadata[metadata_name.upper()] = \
1055 adaptive_det_res_struct.includeThresholdMultiplier
1056 self.metadata[metadata_name] = adaptive_det_res_struct.includeThresholdMultiplier
1061 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
1062 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
1064 shapelets_iq_score=result.exposure.info.getSummaryStats().shapeletsIqScore,
1065 n_sources=len(result.psf_stars_footprints),
1066 psf_shape_ixx=psf_shape.getIxx(),
1067 psf_shape_iyy=psf_shape.getIyy(),
1068 psf_shape_ixy=psf_shape.getIxy(),
1069 psf_size=psf_shape.getDeterminantRadius(),
1072 if result.background
is None:
1073 result.background = afwMath.BackgroundList()
1076 result.psf_stars = result.psf_stars_footprints.asAstropy()
1079 afwTable.updateSourceCoords(
1080 result.exposure.wcs,
1081 sourceList=result.psf_stars_footprints,
1082 include_covariance=self.config.do_include_astrometric_errors,
1085 result.exposure, result.psf_stars_footprints, exposure_region=exposure_region
1087 num_astrometry_matches = len(astrometry_matches)
1088 if "astrometry_matches" in self.config.optional_outputs:
1091 result.psf_stars = result.psf_stars_footprints.asAstropy()
1095 if self.config.doMaskDiffractionSpikes:
1096 self.diffractionSpikeMask.run(result.exposure)
1098 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
1099 if self.config.do_remeasure_star_background
and self.config.do_adaptive_threshold_detection:
1102 background_to_photometric_ratio=result.background_to_photometric_ratio,
1110 background_to_photometric_ratio=result.background_to_photometric_ratio,
1111 adaptive_det_res_struct=adaptive_det_res_struct,
1112 num_astrometry_matches=num_astrometry_matches,
1114 psf = result.exposure.getPsf()
1115 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1116 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1120 afwTable.updateSourceCoords(
1121 result.exposure.wcs,
1122 sourceList=result.stars_footprints,
1123 include_covariance=self.config.do_include_astrometric_errors,
1126 summary_stat_catalog = result.stars_footprints
1127 result.stars = result.stars_footprints.asAstropy()
1128 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1133 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1134 result.stars = result.stars_footprints.asAstropy()
1135 have_fit_astrometry =
True
1137 result.stars_footprints, photometry_matches, \
1138 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1139 have_fit_photometry =
True
1140 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1143 result.stars = result.stars_footprints.asAstropy()
1147 summary_stat_catalog = result.stars_footprints
1148 if "photometry_matches" in self.config.optional_outputs:
1151 if "mask" in self.config.optional_outputs:
1152 result.mask = result.exposure.mask.clone()
1153 except pipeBase.AlgorithmError:
1154 if not have_fit_psf:
1155 result.exposure.setPsf(
None)
1156 if not have_fit_astrometry:
1157 result.exposure.setWcs(
None)
1158 if not have_fit_photometry:
1159 result.exposure.setPhotoCalib(
None)
1166 self.
_summarize(result.exposure, summary_stat_catalog, result.background,
1167 summary=result.exposure.info.getSummaryStats())
1170 self.
_summarize(result.exposure, summary_stat_catalog, result.background,
1171 summary=result.exposure.info.getSummaryStats())
1176 bgIgnoredPixelMasks = lsst.meas.algorithms.SubtractBackgroundConfig().ignoredPixelMask.list()
1177 for maskName
in self.config.background_stats_ignored_pixel_masks:
1178 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1179 and maskName
not in bgIgnoredPixelMasks):
1180 bgIgnoredPixelMasks += [maskName]
1182 statsCtrl = afwMath.StatisticsControl()
1183 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(bgIgnoredPixelMasks))
1185 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.MEDIAN, statsCtrl)
1186 median_bg, _ = statObj.getResult(afwMath.MEDIAN)
1187 self.metadata[
"bg_subtracted_skyPixel_instFlux_median"] = median_bg
1189 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.STDEV, statsCtrl)
1190 stdev_bg, _ = statObj.getResult(afwMath.STDEV)
1191 self.metadata[
"bg_subtracted_skyPixel_instFlux_stdev"] = stdev_bg
1193 self.metadata[
"bg_subtracted_skySource_flux_median"] = (
1194 np.median(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1196 self.metadata[
"bg_subtracted_skySource_flux_stdev"] = (
1197 np.std(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1200 if self.config.do_calibrate_pixels:
1204 background_to_photometric_ratio=result.background_to_photometric_ratio,
1206 result.applied_photo_calib = photo_calib
1208 result.applied_photo_calib =
None
1212 if self.config.run_sattle:
1216 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1217 historical=self.config.sattle_historical)
1218 self.log.info(
'Successfully triggered load of sattle visit cache')
1219 except requests.exceptions.HTTPError:
1220 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1270 """Find bright sources detected on an exposure and fit a PSF model to
1271 them, repairing likely cosmic rays before detection.
1273 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1274 model does not include contributions from cosmic rays.
1278 result : `lsst.pipe.base.Struct`
1279 Result struct that is modified to allow saving of partial outputs
1280 for some failure conditions. Should contain at least the following
1283 - exposure : `lsst.afw.image.Exposure`
1284 Exposure to detect and measure bright stars on.
1285 - background : `lsst.afw.math.BackgroundList` | `None`
1286 Background that was fit to the exposure during detection.
1287 - background_to_photometric_ratio : `lsst.afw.image.Image` | `None`
1288 Image to convert photometric-flattened image to
1289 background-flattened image.
1290 id_generator : `lsst.meas.base.IdGenerator`
1291 Object that generates source IDs and provides random seeds.
1295 sources : `lsst.afw.table.SourceCatalog`
1296 Catalog of detected bright sources.
1297 cell_set : `lsst.afw.math.SpatialCellSet`
1298 PSF candidates returned by the psf determiner.
1299 adaptive_det_res_struct : `lsst.pipe.base.Struct`
1300 Result struct from the adaptive threshold detection.
1304 This method modifies the exposure, background and
1305 background_to_photometric_ratio attributes of the result struct
1308 def log_psf(msg, addToMetadata=False):
1309 """Log the parameters of the psf and background, with a prepended
1310 message. There is also the option to add the PSF sigma to the task
1316 Message to prepend the log info with.
1317 addToMetadata : `bool`, optional
1318 Whether to add the final psf sigma value to the task
1319 metadata (the default is False).
1321 position = result.exposure.psf.getAveragePosition()
1322 sigma = result.exposure.psf.computeShape(position).getDeterminantRadius()
1323 dimensions = result.exposure.psf.computeImage(position).getDimensions()
1324 if result.background
is not None:
1325 median_background = np.median(result.background.getImage().array)
1327 median_background = 0.0
1328 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1329 msg, sigma, dimensions, median_background)
1331 self.metadata[
"final_psf_sigma"] = sigma
1333 self.log.info(
"First pass detection with Gaussian PSF FWHM=%s pixels",
1334 self.config.install_simple_psf.fwhm)
1335 self.install_simple_psf.run(exposure=result.exposure)
1337 result.background = self.psf_subtract_background.run(
1338 exposure=result.exposure,
1339 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1341 log_psf(
"Initial PSF:")
1342 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1344 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1345 if not self.config.do_adaptive_threshold_detection:
1346 adaptive_det_res_struct =
None
1349 detections = self.psf_detection.run(
1351 exposure=result.exposure,
1352 background=result.background,
1353 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1356 initialThreshold = self.config.psf_detection.thresholdValue
1357 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1358 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1359 table, result.exposure,
1360 initialThreshold=initialThreshold,
1361 initialThresholdMultiplier=initialThresholdMultiplier,
1363 detections = adaptive_det_res_struct.detections
1365 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1366 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1367 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1368 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1370 if self.config.do_compute_shapelet_decomposition:
1373 self.log.info(
"Computing rough shapelet decomposition for PSF star candidates.")
1374 seed = id_generator.catalog_id & 0xFFFFFFFF
1375 shapelets_results = self.compute_shapelet_decomposition.run(
1376 masked_image=result.exposure.getMaskedImage(), catalog=detections.sources, seed=seed
1378 result.exposure.info.setSummaryStats(shapelets_results.shapelets_summary)
1379 detections.sources = shapelets_results.catalog
1381 self.psf_source_measurement.run(detections.sources, result.exposure)
1383 if self.config.do_compute_shapelet_decomposition:
1389 self.log.info(
"Computing shapelets_iq_score that includes power from the "
1390 "median centroid offset.")
1391 centroid_offset_scale = 0.5*np.sqrt(
1392 self.config.compute_shapelet_decomposition.max_footprint_area)
1393 shapelets_results = self.compute_shapelet_decomposition.compute_shapelets_iq_score(
1394 shapelets_results=shapelets_results,
1395 catalog=detections.sources,
1396 centroid_offset_scale=centroid_offset_scale,
1397 shapelets_base_name=self.compute_shapelet_decomposition.shapelets_base_name,
1400 result.exposure.info.setSummaryStats(shapelets_results.shapelets_summary)
1402 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1409 if not self.config.do_adaptive_threshold_detection:
1413 self.install_simple_psf.run(exposure=result.exposure)
1415 log_psf(
"Rerunning with simple PSF:")
1423 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1426 detections = self.psf_detection.run(
1428 exposure=result.exposure,
1429 background=result.background,
1430 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1432 self.psf_source_measurement.run(detections.sources, result.exposure)
1433 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1434 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1435 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1436 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1437 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1438 log_psf(
"Final PSF:", addToMetadata=
True)
1441 self.psf_repair.run(exposure=result.exposure)
1443 self.psf_source_measurement.run(detections.sources, result.exposure)
1447 return pipeBase.Struct(
1448 detections_sources=detections.sources,
1449 psf_result_cellSet=psf_result.cellSet,
1450 adaptive_det_res_struct=adaptive_det_res_struct,
1483 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1484 adaptive_det_res_struct=None, num_astrometry_matches=None):
1485 """Detect stars on an exposure that has a PSF model, and measure their
1486 PSF, circular aperture, compensated gaussian fluxes.
1490 exposure : `lsst.afw.image.Exposure`
1491 Exposure to detect and measure stars on.
1492 background : `lsst.afw.math.BackgroundList`
1493 Background that was fit to the exposure during detection;
1494 modified in-place during subsequent detection.
1495 id_generator : `lsst.meas.base.IdGenerator`
1496 Object that generates source IDs and provides random seeds.
1497 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1498 Image to convert photometric-flattened image to
1499 background-flattened image.
1503 stars : `SourceCatalog`
1504 Sources that are very likely to be stars, with a limited set of
1505 measurements performed on them.
1508 id_generator.make_table_id_factory())
1510 maxAdaptiveDetIter = 8
1512 if adaptive_det_res_struct
is not None:
1513 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1514 adaptiveDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1515 if self.config.do_remeasure_star_background:
1516 adaptiveDetectionConfig.reEstimateBackground =
False
1518 adaptiveDetectionConfig.reEstimateBackground =
True
1519 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1521 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1523 adaptiveDetectionConfig.thresholdValue = max(
1524 self.config.star_detection.thresholdValue,
1525 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1527 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1528 "thresholdValue = %.2f and multiplier = %.1f",
1529 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1530 adaptiveDetectionConfig.includeThresholdMultiplier)
1531 adaptiveDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1532 config=adaptiveDetectionConfig
1534 detections = adaptiveDetectionTask.run(
1537 background=background,
1538 backgroundToPhotometricRatio=background_to_photometric_ratio,
1540 nFootprint = len(detections.sources)
1543 for src
in detections.sources:
1544 nPeakSrc = len(src.getFootprint().getPeaks())
1548 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1550 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1551 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1552 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1553 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1554 threshFactor = max(0.01, 1.5*threshFactor)
1555 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1556 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1560 threshFactor *= 0.75
1561 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1562 "factor to %.2f. and rerunning.", threshFactor)
1566 detections = self.star_detection.run(
1569 background=background,
1570 backgroundToPhotometricRatio=background_to_photometric_ratio,
1572 sources = detections.sources
1573 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1575 n_sky_sources = np.sum(sources[
"sky_source"])
1576 if (self.config.do_downsample_footprints
1577 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1578 if exposure.info.id
is None:
1579 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1582 seed = exposure.info.id & 0xFFFFFFFF
1584 gen = np.random.RandomState(seed)
1587 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1588 indices = gen.choice(
1590 size=self.config.downsample_max_footprints,
1593 skyIndices, = np.where(sources[
"sky_source"])
1594 indices = np.concatenate((indices, skyIndices))
1596 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1598 sel = np.zeros(len(sources), dtype=bool)
1600 sources = sources[sel]
1604 self.star_deblend.run(exposure=exposure, sources=sources)
1607 if not sources.isContiguous():
1608 sources = sources.copy(deep=
True)
1611 self.star_measurement.run(sources, exposure)
1612 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1613 self.metadata[
"failed_deblend_source_count"] = np.sum(
1614 ~sources[
"sky_source"] & sources[
"deblend_failed"]
1616 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1617 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1622 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1623 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1624 self.star_catalog_calculation.run(sources)
1625 self.star_set_primary_flags.run(sources)
1627 result = self.star_selector.run(sources)
1629 if not result.sourceCat.isContiguous():
1630 return result.sourceCat.copy(deep=
True)
1632 return result.sourceCat
2011 """Remeasure the exposure's background with iterative adaptive
2012 threshold detection.
2016 result : `lsst.pipe.base.Struct`
2017 The current state of the result Strut from the run method of
2018 CalibrateImageTask. Will be modified in place.
2019 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
2020 Image to convert photometric-flattened image to
2021 background-flattened image.
2025 result : `lsst.pipe.base.Struct`
2026 The modified result Struct with the new background subtracted.
2031 backgroundOrig = result.background.clone()
2032 median_background_orig = np.median(backgroundOrig.getImage().array)
2033 self.log.info(
"Original median_background = %.2f", median_background_orig)
2034 result.exposure.image.array += result.background.getImage().array
2035 result.background = afwMath.BackgroundList()
2037 origMask = result.exposure.mask.clone()
2038 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
2039 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
2041 detected_mask_planes,
2043 minDetFracForFinalBg = 0.02
2044 maxDetFracForFinalBg = 0.93
2048 maxAdaptiveDetIter = 10
2049 for dilateIter
in range(maxAdaptiveDetIter):
2050 dilatedMask = origMask.clone()
2051 for maskName
in detected_mask_planes:
2053 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2054 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2055 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2056 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2058 detectedMask = dilatedMask.getMaskPlane(maskName)
2059 dilatedMask.clearMaskPlane(detectedMask)
2061 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2064 detected_mask_planes,
2066 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
2071 result.exposure.mask = dilatedMask
2072 self.log.debug(
"detected_fraction_orig = %.3f; detected_fraction_dilated = %.3f",
2073 detected_fraction_orig, detected_fraction_dilated)
2074 n_above_max_per_amp = -99
2075 highest_detected_fraction_per_amp = float(
"nan")
2078 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2080 detected_mask_planes, bad_mask_planes)
2081 self.log.debug(
"Dilated mask: n_above_max_per_amp = %d; "
2082 "highest_detected_fraction_per_amp = %.3f",
2083 n_above_max_per_amp, highest_detected_fraction_per_amp)
2085 bgIgnoreMasksToAdd = [
"SAT",
"SUSPECT",
"SPIKE"]
2086 detected_fraction = 1.0
2087 nFootprintTemp = 1e12
2088 starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
2089 for maskName
in bgIgnoreMasksToAdd:
2090 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2091 and maskName
not in starBackgroundDetectionConfig.background.ignoredPixelMask):
2092 starBackgroundDetectionConfig.background.ignoredPixelMask += [maskName]
2093 starBackgroundDetectionConfig.doTempLocalBackground =
False
2094 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
2095 starBackgroundDetectionConfig.reEstimateBackground =
False
2096 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
2097 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background_orig)
2098 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
2100 n_above_max_per_amp = -99
2101 highest_detected_fraction_per_amp = float(
"nan")
2102 doCheckPerAmpDetFraction =
True
2104 minFootprints = self.config.star_background_min_footprints
2106 for nIter
in range(maxIter):
2107 currentThresh = starBackgroundDetectionConfig.thresholdValue
2108 nZeroEncountered = 0
2109 if nFootprintTemp == 0:
2110 zeroFactor = min(0.98, 0.9 + 0.01*nZeroEncountered)
2111 starBackgroundDetectionConfig.thresholdValue = zeroFactor*currentThresh
2112 self.log.warning(
"No footprints detected. Decreasing threshold to %.2f and rerunning.",
2113 starBackgroundDetectionConfig.thresholdValue)
2114 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2115 config=starBackgroundDetectionConfig)
2117 tempDetections = starBackgroundDetectionTask.run(
2118 table=table, exposure=result.exposure, clearMask=
True)
2119 nFootprintTemp = len(tempDetections.sources)
2120 minFootprints = max(self.config.star_background_min_footprints,
2121 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2122 minFootprints = min(200, minFootprints)
2123 nZeroEncountered += 1
2124 if nFootprintTemp >= minFootprints:
2126 detected_mask_planes,
2128 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2129 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2130 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
2131 nIter, starBackgroundDetectionConfig.thresholdValue,
2132 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2133 nFootprintTemp, minFootprints)
2134 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2140 if nFootprintTemp > 0
and nFootprintTemp < minFootprints:
2143 if detected_fraction > maxDetFracForFinalBg
or nFootprintTemp <= minFootprints:
2144 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
2145 if nFootprintTemp < minFootprints
and detected_fraction > 0.9*maxDetFracForFinalBg:
2146 if nFootprintTemp == 1:
2147 starBackgroundDetectionConfig.thresholdValue = 1.4*currentThresh
2149 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
2151 if n_above_max_per_amp > 1:
2152 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
2153 if detected_fraction < minDetFracForFinalBg:
2154 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
2155 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2156 config=starBackgroundDetectionConfig)
2158 tempDetections = starBackgroundDetectionTask.run(
2159 table=table, exposure=result.exposure, clearMask=
True)
2160 result.exposure.mask |= dilatedMask
2161 nFootprintTemp = len(tempDetections.sources)
2162 minFootprints = max(self.config.star_background_min_footprints,
2163 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2164 minFootprints = min(200, minFootprints)
2167 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2168 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2169 "(max is %.3f; min is %.3f); nFootprint = %d (current min is %d)",
2170 nIter, starBackgroundDetectionConfig.thresholdValue,
2171 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2172 nFootprintTemp, minFootprints)
2173 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2175 n_amp = len(result.exposure.detector.getAmplifiers())
2176 if doCheckPerAmpDetFraction:
2177 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2179 detected_mask_planes, bad_mask_planes)
2181 if not no_zero_det_amps:
2182 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
2184 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
2185 and n_above_max_per_amp < int(0.75*n_amp)
2186 and no_zero_det_amps
2187 and nFootprintTemp >= minFootprints):
2188 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
2189 or detected_fraction < 0.85*maxDetFracForFinalBg):
2192 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
2193 self.log.debug(
"Number of amplifiers with detected fraction above the maximum "
2194 "(%.2f) excedes the maximum allowed (%d >= %d). Making a small "
2195 "tweak to the threshold (from %.2f to %.2f) and rerunning.",
2196 maxDetFracForFinalBg, n_above_max_per_amp, int(0.75*n_amp),
2197 currentThresh, starBackgroundDetectionConfig.thresholdValue)
2198 self.log.debug(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
2202 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
2203 "(highest per amp section = %.5f)",
2204 detected_fraction, highest_detected_fraction_per_amp)
2206 if detected_fraction > maxDetFracForFinalBg:
2207 result.exposure.mask = dilatedMask
2208 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
2209 "was too large in star_background_detection = %.3f (max = %.3f). "
2210 "Reverting to dilated mask from PSF detection.",
2211 detected_fraction, maxDetFracForFinalBg)
2212 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
2213 star_background = self.star_background.run(
2214 exposure=result.exposure,
2215 backgroundToPhotometricRatio=background_to_photometric_ratio,
2217 result.background.append(star_background[0])
2219 median_background = np.median(result.background.getImage().array)
2220 self.log.info(
"New initial median_background = %.2f", median_background)
2228 if detected_fraction < 0.5:
2229 dilatedMask = result.exposure.mask.clone()
2230 for maskName
in detected_mask_planes:
2232 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2233 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2234 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2235 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2237 detectedMask = dilatedMask.getMaskPlane(maskName)
2238 dilatedMask.clearMaskPlane(detectedMask)
2240 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2243 detected_mask_planes,
2245 result.exposure.mask = dilatedMask
2246 self.log.debug(
"detected_fraction_orig = %.3f; detected_fraction_dilated = %.3f",
2247 detected_fraction_orig, detected_fraction_dilated)
2249 bbox = result.exposure.getBBox()
2260 pedestalBackgroundConfig = lsst.meas.algorithms.SubtractBackgroundConfig()
2261 for maskName
in bgIgnoreMasksToAdd:
2262 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2263 and maskName
not in pedestalBackgroundConfig.ignoredPixelMask):
2264 pedestalBackgroundConfig.ignoredPixelMask += [maskName]
2265 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
2266 pedestalBackgroundConfig.approxOrderX = 0
2267 pedestalBackgroundConfig.binSize = min(32, int(math.ceil(max(bbox.width, bbox.height)/4.0)))
2268 self.log.info(
"Initial pedestal binSize = %d pixels", pedestalBackgroundConfig.binSize)
2269 cumulativePedestalLevel = 0.0
2272 relativeStoppingCriterion = 0.05
2276 absoluteStoppingCriterion = 0.5
2277 inPedestalIteration =
True
2278 while inPedestalIteration:
2279 cumulativePedestalPrev = cumulativePedestalLevel
2280 pedestalBackgroundTask = lsst.meas.algorithms.SubtractBackgroundTask(
2281 config=pedestalBackgroundConfig)
2282 pedestalBackgroundList = pedestalBackgroundTask.run(
2283 exposure=result.exposure,
2284 background=result.background,
2285 backgroundToPhotometricRatio=background_to_photometric_ratio,
2289 pedestalBackground = afwMath.BackgroundList()
2290 pedestalBackground.append(pedestalBackgroundList[-1])
2291 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
2292 cumulativePedestalLevel += pedestalBackgroundLevel
2293 absoluteDelta = abs(cumulativePedestalLevel - cumulativePedestalPrev)
2294 if cumulativePedestalPrev != 0.0:
2295 relativeDelta = abs(absoluteDelta/cumulativePedestalPrev)
2298 self.log.info(
"Subtracted pedestalBackgroundLevel = %.4f (cumulative = %.4f; "
2299 "relativeDelta = %.4f; absoluteDelta = %.3f)",
2300 pedestalBackgroundLevel, cumulativePedestalLevel,
2301 relativeDelta, absoluteDelta)
2303 if (relativeDelta < relativeStoppingCriterion
2304 or absoluteDelta < absoluteStoppingCriterion):
2306 inPedestalIteration =
False
2309 if pedestalBackgroundConfig.binSize*2 < 2*max(bbox.width, bbox.height):
2310 pedestalBackgroundConfig.binSize = int(pedestalBackgroundConfig.binSize*2)
2311 self.log.info(
"Increasing pedestal binSize to %d pixels and remeasuring.",
2312 pedestalBackgroundConfig.binSize)
2315 self.log.warning(
"Reached maximum bin size. Exiting pedestal loop without meeting "
2316 "the convergence criteria (relativeDelta <= %.2f or "
2317 "absoluteDelta <= %.2f).",
2318 relativeStoppingCriterion, absoluteStoppingCriterion)
2319 inPedestalIteration =
False
2320 self.log.info(
"Final subtracted cumulativePedestalBackgroundLevel = %.4f", cumulativePedestalLevel)
2323 mask = result.exposure.mask
2324 for mp
in detected_mask_planes:
2325 if mp
not in mask.getMaskPlaneDict():
2326 mask.addMaskPlane(mp)
2327 mask &= ~mask.getPlaneBitMask(detected_mask_planes)