809 inputs = butlerQC.get(inputRefs)
810 exposures = inputs.pop(
"exposures")
812 exposure_record = inputRefs.exposures[0].dataId.records[
"exposure"]
813 if self.config.useButlerExposureRegion:
814 exposure_region = butlerQC.quantum.dataId.region
816 exposure_region =
None
818 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
820 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
821 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
822 refCats=inputs.pop(
"astrometry_ref_cat"),
823 name=self.config.connections.astrometry_ref_cat,
824 config=self.config.astrometry_ref_loader, log=self.log)
825 self.astrometry.setRefObjLoader(astrometry_loader)
827 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
828 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
829 refCats=inputs.pop(
"photometry_ref_cat"),
830 name=self.config.connections.photometry_ref_cat,
831 config=self.config.photometry_ref_loader, log=self.log)
832 self.photometry.match.setRefObjLoader(photometry_loader)
834 if self.config.doMaskDiffractionSpikes:
837 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
839 if self.config.do_illumination_correction:
840 background_flat = inputs.pop(
"background_flat")
841 illumination_correction = inputs.pop(
"illumination_correction")
843 background_flat =
None
844 illumination_correction =
None
847 if self.config.useButlerCamera:
848 if "camera_model" in inputs:
849 camera_model = inputs.pop(
"camera_model")
851 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
852 "astrometry fit will use the WCS already attached to the exposure.",
853 exposures[0].filter.bandLabel)
856 assert not inputs,
"runQuantum got more inputs than expected"
860 result = pipeBase.Struct(
862 stars_footprints=
None,
863 psf_stars_footprints=
None,
864 background_to_photometric_ratio=
None,
870 id_generator=id_generator,
871 background_flat=background_flat,
872 illumination_correction=illumination_correction,
873 camera_model=camera_model,
874 exposure_record=exposure_record,
875 exposure_region=exposure_region,
877 except pipeBase.AlgorithmError
as e:
878 error = pipeBase.AnnotatedPartialOutputsError.annotate(
882 result.psf_stars_footprints,
883 result.stars_footprints,
886 butlerQC.put(result, outputRefs)
889 butlerQC.put(result, outputRefs)
898 background_flat=None,
899 illumination_correction=None,
901 exposure_record=None,
902 exposure_region=None,
904 """Find stars and perform psf measurement, then do a deeper detection
905 and measurement and calibrate astrometry and photometry from that.
909 exposures : `lsst.afw.image.Exposure` or \
910 `list` [`lsst.afw.image.Exposure`]
911 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
912 Modified in-place during processing if only one is passed.
913 If two exposures are passed, treat them as snaps and combine
914 before doing further processing.
915 id_generator : `lsst.meas.base.IdGenerator`, optional
916 Object that generates source IDs and provides random seeds.
917 result : `lsst.pipe.base.Struct`, optional
918 Result struct that is modified to allow saving of partial outputs
919 for some failure conditions. If the task completes successfully,
920 this is also returned.
921 background_flat : `lsst.afw.image.Exposure`, optional
922 Background flat-field image.
923 illumination_correction : `lsst.afw.image.Exposure`, optional
924 Illumination correction image.
925 camera_model : `lsst.afw.cameraGeom.Camera`, optional
926 Camera to be used if constructing updated WCS.
927 exposure_record : `lsst.daf.butler.DimensionRecord`, optional
928 Butler metadata for the ``exposure`` dimension. Used if
929 constructing an updated WCS instead of the boresight and
930 orientation angle from the visit info.
931 exposure_region : `lsst.sphgeom.Region`, optional
932 The exposure region to use for the reference catalog filtering.
933 If `None`, this region will be set as a padded bbox + current WCS
938 result : `lsst.pipe.base.Struct`
939 Results as a struct with attributes:
942 Calibrated exposure, with pixels in nJy units.
943 (`lsst.afw.image.Exposure`)
945 Stars that were used to calibrate the exposure, with
946 calibrated fluxes and magnitudes.
947 (`astropy.table.Table`)
949 Footprints of stars that were used to calibrate the exposure.
950 (`lsst.afw.table.SourceCatalog`)
952 Stars that were used to determine the image PSF.
953 (`astropy.table.Table`)
954 ``psf_stars_footprints``
955 Footprints of stars that were used to determine the image PSF.
956 (`lsst.afw.table.SourceCatalog`)
958 Background that was fit to the exposure when detecting
959 ``stars``. (`lsst.afw.math.BackgroundList`)
960 ``applied_photo_calib``
961 Photometric calibration that was fit to the star catalog and
962 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
963 This is `None` if ``config.do_calibrate_pixels`` is `False`.
964 ``astrometry_matches``
965 Reference catalog stars matches used in the astrometric fit.
966 (`list` [`lsst.afw.table.ReferenceMatch`] or
967 `lsst.afw.table.BaseCatalog`).
968 ``photometry_matches``
969 Reference catalog stars matches used in the photometric fit.
970 (`list` [`lsst.afw.table.ReferenceMatch`] or
971 `lsst.afw.table.BaseCatalog`).
973 Copy of the mask plane of `exposure`.
974 (`lsst.afw.image.Mask`)
977 result = pipeBase.Struct()
978 if id_generator
is None:
979 id_generator = lsst.meas.base.IdGenerator()
981 result.exposure = self.snap_combine.run(exposures).exposure
982 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
984 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
987 if self.config.do_illumination_correction:
988 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
989 raise pipeBase.InvalidQuantumError(
990 "Cannot use do_illumination_correction with an image that has not had a flat applied",
999 result.exposure, camera_model, exposure_record=exposure_record
1001 elif exposure_record
is not None:
1004 result.background =
None
1005 result.background_to_photometric_ratio =
None
1006 summary_stat_catalog =
None
1011 have_fit_psf =
False
1012 have_fit_astrometry =
False
1013 have_fit_photometry =
False
1018 illumination_correction,
1021 result.psf_stars_footprints, _, adaptive_det_res_struct = self.
_compute_psf(
1030 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
1031 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
1033 n_sources=len(result.psf_stars_footprints),
1034 psf_shape_ixx=psf_shape.getIxx(),
1035 psf_shape_iyy=psf_shape.getIyy(),
1036 psf_shape_ixy=psf_shape.getIxy(),
1037 psf_size=psf_shape.getDeterminantRadius(),
1040 if result.background
is None:
1041 result.background = afwMath.BackgroundList()
1044 result.psf_stars = result.psf_stars_footprints.asAstropy()
1047 afwTable.updateSourceCoords(
1048 result.exposure.wcs,
1049 sourceList=result.psf_stars_footprints,
1050 include_covariance=self.config.do_include_astrometric_errors,
1053 result.exposure, result.psf_stars_footprints, exposure_region=exposure_region
1055 num_astrometry_matches = len(astrometry_matches)
1056 if "astrometry_matches" in self.config.optional_outputs:
1059 result.psf_stars = result.psf_stars_footprints.asAstropy()
1063 if self.config.doMaskDiffractionSpikes:
1064 self.diffractionSpikeMask.run(result.exposure)
1066 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
1067 if self.config.do_adaptive_threshold_detection:
1070 background_to_photometric_ratio=result.background_to_photometric_ratio,
1078 background_to_photometric_ratio=result.background_to_photometric_ratio,
1079 adaptive_det_res_struct=adaptive_det_res_struct,
1080 num_astrometry_matches=num_astrometry_matches,
1082 psf = result.exposure.getPsf()
1083 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1084 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1088 afwTable.updateSourceCoords(
1089 result.exposure.wcs,
1090 sourceList=result.stars_footprints,
1091 include_covariance=self.config.do_include_astrometric_errors,
1094 summary_stat_catalog = result.stars_footprints
1095 result.stars = result.stars_footprints.asAstropy()
1096 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1101 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1102 result.stars = result.stars_footprints.asAstropy()
1103 have_fit_astrometry =
True
1105 result.stars_footprints, photometry_matches, \
1106 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1107 have_fit_photometry =
True
1108 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1111 result.stars = result.stars_footprints.asAstropy()
1115 summary_stat_catalog = result.stars_footprints
1116 if "photometry_matches" in self.config.optional_outputs:
1119 if "mask" in self.config.optional_outputs:
1120 result.mask = result.exposure.mask.clone()
1121 except pipeBase.AlgorithmError:
1122 if not have_fit_psf:
1123 result.exposure.setPsf(
None)
1124 if not have_fit_astrometry:
1125 result.exposure.setWcs(
None)
1126 if not have_fit_photometry:
1127 result.exposure.setPhotoCalib(
None)
1134 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1137 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1142 bgIgnoredPixelMasks = lsst.meas.algorithms.SubtractBackgroundConfig().ignoredPixelMask.list()
1143 for maskName
in self.config.background_stats_ignored_pixel_masks:
1144 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1145 and maskName
not in bgIgnoredPixelMasks):
1146 bgIgnoredPixelMasks += [maskName]
1148 statsCtrl = afwMath.StatisticsControl()
1149 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(bgIgnoredPixelMasks))
1151 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.MEDIAN, statsCtrl)
1152 median_bg, _ = statObj.getResult(afwMath.MEDIAN)
1153 self.metadata[
"bg_subtracted_skyPixel_instFlux_median"] = median_bg
1155 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.STDEV, statsCtrl)
1156 stdev_bg, _ = statObj.getResult(afwMath.STDEV)
1157 self.metadata[
"bg_subtracted_skyPixel_instFlux_stdev"] = stdev_bg
1159 self.metadata[
"bg_subtracted_skySource_flux_median"] = (
1160 np.median(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1162 self.metadata[
"bg_subtracted_skySource_flux_stdev"] = (
1163 np.std(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1166 if self.config.do_calibrate_pixels:
1170 background_to_photometric_ratio=result.background_to_photometric_ratio,
1172 result.applied_photo_calib = photo_calib
1174 result.applied_photo_calib =
None
1178 if self.config.run_sattle:
1182 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1183 historical=self.config.sattle_historical)
1184 self.log.info(
'Successfully triggered load of sattle visit cache')
1185 except requests.exceptions.HTTPError:
1186 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1236 """Find bright sources detected on an exposure and fit a PSF model to
1237 them, repairing likely cosmic rays before detection.
1239 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1240 model does not include contributions from cosmic rays.
1244 result : `lsst.pipe.base.Struct`
1245 Result struct that is modified to allow saving of partial outputs
1246 for some failure conditions. Should contain at least the following
1249 - exposure : `lsst.afw.image.Exposure`
1250 Exposure to detect and measure bright stars on.
1251 - background : `lsst.afw.math.BackgroundList` | `None`
1252 Background that was fit to the exposure during detection.
1253 - background_to_photometric_ratio : `lsst.afw.image.Image` | `None`
1254 Image to convert photometric-flattened image to
1255 background-flattened image.
1256 id_generator : `lsst.meas.base.IdGenerator`
1257 Object that generates source IDs and provides random seeds.
1261 sources : `lsst.afw.table.SourceCatalog`
1262 Catalog of detected bright sources.
1263 cell_set : `lsst.afw.math.SpatialCellSet`
1264 PSF candidates returned by the psf determiner.
1265 adaptive_det_res_struct : `lsst.pipe.base.Struct`
1266 Result struct from the adaptive threshold detection.
1270 This method modifies the exposure, background and
1271 background_to_photometric_ratio attributes of the result struct
1274 def log_psf(msg, addToMetadata=False):
1275 """Log the parameters of the psf and background, with a prepended
1276 message. There is also the option to add the PSF sigma to the task
1282 Message to prepend the log info with.
1283 addToMetadata : `bool`, optional
1284 Whether to add the final psf sigma value to the task
1285 metadata (the default is False).
1287 position = result.exposure.psf.getAveragePosition()
1288 sigma = result.exposure.psf.computeShape(position).getDeterminantRadius()
1289 dimensions = result.exposure.psf.computeImage(position).getDimensions()
1290 if result.background
is not None:
1291 median_background = np.median(result.background.getImage().array)
1293 median_background = 0.0
1294 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1295 msg, sigma, dimensions, median_background)
1297 self.metadata[
"final_psf_sigma"] = sigma
1299 self.log.info(
"First pass detection with Gaussian PSF FWHM=%s pixels",
1300 self.config.install_simple_psf.fwhm)
1301 self.install_simple_psf.run(exposure=result.exposure)
1303 result.background = self.psf_subtract_background.run(
1304 exposure=result.exposure,
1305 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1307 log_psf(
"Initial PSF:")
1308 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1310 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1311 if not self.config.do_adaptive_threshold_detection:
1312 adaptive_det_res_struct =
None
1315 detections = self.psf_detection.run(
1317 exposure=result.exposure,
1318 background=result.background,
1319 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1322 initialThreshold = self.config.psf_detection.thresholdValue
1323 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1324 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1325 table, result.exposure,
1326 initialThreshold=initialThreshold,
1327 initialThresholdMultiplier=initialThresholdMultiplier,
1329 detections = adaptive_det_res_struct.detections
1331 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1332 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1333 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1334 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1335 self.psf_source_measurement.run(detections.sources, result.exposure)
1336 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1343 if not self.config.do_adaptive_threshold_detection:
1347 self.install_simple_psf.run(exposure=result.exposure)
1349 log_psf(
"Rerunning with simple PSF:")
1357 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1360 detections = self.psf_detection.run(
1362 exposure=result.exposure,
1363 background=result.background,
1364 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1366 self.psf_source_measurement.run(detections.sources, result.exposure)
1367 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1368 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1369 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1370 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1371 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1372 log_psf(
"Final PSF:", addToMetadata=
True)
1375 self.psf_repair.run(exposure=result.exposure)
1377 self.psf_source_measurement.run(detections.sources, result.exposure)
1381 return detections.sources, psf_result.cellSet, adaptive_det_res_struct
1413 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1414 adaptive_det_res_struct=None, num_astrometry_matches=None):
1415 """Detect stars on an exposure that has a PSF model, and measure their
1416 PSF, circular aperture, compensated gaussian fluxes.
1420 exposure : `lsst.afw.image.Exposure`
1421 Exposure to detect and measure stars on.
1422 background : `lsst.afw.math.BackgroundList`
1423 Background that was fit to the exposure during detection;
1424 modified in-place during subsequent detection.
1425 id_generator : `lsst.meas.base.IdGenerator`
1426 Object that generates source IDs and provides random seeds.
1427 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1428 Image to convert photometric-flattened image to
1429 background-flattened image.
1433 stars : `SourceCatalog`
1434 Sources that are very likely to be stars, with a limited set of
1435 measurements performed on them.
1438 id_generator.make_table_id_factory())
1440 maxAdaptiveDetIter = 8
1442 if adaptive_det_res_struct
is not None:
1443 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1444 adaptiveDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1445 adaptiveDetectionConfig.reEstimateBackground =
False
1446 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1448 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1450 adaptiveDetectionConfig.thresholdValue = max(
1451 self.config.star_detection.thresholdValue,
1452 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1454 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1455 "thresholdValue = %.2f and multiplier = %.1f",
1456 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1457 adaptiveDetectionConfig.includeThresholdMultiplier)
1458 adaptiveDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1459 config=adaptiveDetectionConfig
1461 detections = adaptiveDetectionTask.run(
1464 background=background,
1465 backgroundToPhotometricRatio=background_to_photometric_ratio,
1467 nFootprint = len(detections.sources)
1470 for src
in detections.sources:
1471 nPeakSrc = len(src.getFootprint().getPeaks())
1475 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1477 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1478 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1479 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1480 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1481 threshFactor = max(0.01, 1.5*threshFactor)
1482 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1483 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1487 threshFactor *= 0.75
1488 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1489 "factor to %.2f. and rerunning.", threshFactor)
1493 detections = self.star_detection.run(
1496 background=background,
1497 backgroundToPhotometricRatio=background_to_photometric_ratio,
1499 sources = detections.sources
1500 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1502 n_sky_sources = np.sum(sources[
"sky_source"])
1503 if (self.config.do_downsample_footprints
1504 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1505 if exposure.info.id
is None:
1506 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1509 seed = exposure.info.id & 0xFFFFFFFF
1511 gen = np.random.RandomState(seed)
1514 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1515 indices = gen.choice(
1517 size=self.config.downsample_max_footprints,
1520 skyIndices, = np.where(sources[
"sky_source"])
1521 indices = np.concatenate((indices, skyIndices))
1523 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1525 sel = np.zeros(len(sources), dtype=bool)
1527 sources = sources[sel]
1531 self.star_deblend.run(exposure=exposure, sources=sources)
1534 if not sources.isContiguous():
1535 sources = sources.copy(deep=
True)
1538 self.star_measurement.run(sources, exposure)
1539 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1540 self.metadata[
"failed_deblend_source_count"] = np.sum(
1541 ~sources[
"sky_source"] & sources[
"deblend_failed"]
1543 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1544 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1549 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1550 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1551 self.star_catalog_calculation.run(sources)
1552 self.star_set_primary_flags.run(sources)
1554 result = self.star_selector.run(sources)
1556 if not result.sourceCat.isContiguous():
1557 return result.sourceCat.copy(deep=
True)
1559 return result.sourceCat
1938 """Remeasure the exposure's background with iterative adaptive
1939 threshold detection.
1943 result : `lsst.pipe.base.Struct`
1944 The current state of the result Strut from the run method of
1945 CalibrateImageTask. Will be modified in place.
1946 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1947 Image to convert photometric-flattened image to
1948 background-flattened image.
1952 result : `lsst.pipe.base.Struct`
1953 The modified result Struct with the new background subtracted.
1958 backgroundOrig = result.background.clone()
1959 median_background_orig = np.median(backgroundOrig.getImage().array)
1960 self.log.info(
"Original median_background = %.2f", median_background_orig)
1961 result.exposure.image.array += result.background.getImage().array
1962 result.background = afwMath.BackgroundList()
1964 origMask = result.exposure.mask.clone()
1965 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
1966 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
1968 detected_mask_planes,
1970 minDetFracForFinalBg = 0.02
1971 maxDetFracForFinalBg = 0.93
1975 maxAdaptiveDetIter = 10
1976 for dilateIter
in range(maxAdaptiveDetIter):
1977 dilatedMask = origMask.clone()
1978 for maskName
in detected_mask_planes:
1980 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1981 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1982 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1983 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1985 detectedMask = dilatedMask.getMaskPlane(maskName)
1986 dilatedMask.clearMaskPlane(detectedMask)
1988 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1991 detected_mask_planes,
1993 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
1998 result.exposure.mask = dilatedMask
1999 self.log.debug(
"detected_fraction_orig = %.3f; detected_fraction_dilated = %.3f",
2000 detected_fraction_orig, detected_fraction_dilated)
2001 n_above_max_per_amp = -99
2002 highest_detected_fraction_per_amp = float(
"nan")
2005 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2007 detected_mask_planes, bad_mask_planes)
2008 self.log.debug(
"Dilated mask: n_above_max_per_amp = %d; "
2009 "highest_detected_fraction_per_amp = %.3f",
2010 n_above_max_per_amp, highest_detected_fraction_per_amp)
2012 bgIgnoreMasksToAdd = [
"SAT",
"SUSPECT",
"SPIKE"]
2013 detected_fraction = 1.0
2014 nFootprintTemp = 1e12
2015 starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
2016 for maskName
in bgIgnoreMasksToAdd:
2017 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2018 and maskName
not in starBackgroundDetectionConfig.background.ignoredPixelMask):
2019 starBackgroundDetectionConfig.background.ignoredPixelMask += [maskName]
2020 starBackgroundDetectionConfig.doTempLocalBackground =
False
2021 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
2022 starBackgroundDetectionConfig.reEstimateBackground =
False
2023 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
2024 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background_orig)
2025 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
2027 n_above_max_per_amp = -99
2028 highest_detected_fraction_per_amp = float(
"nan")
2029 doCheckPerAmpDetFraction =
True
2031 minFootprints = self.config.star_background_min_footprints
2033 for nIter
in range(maxIter):
2034 currentThresh = starBackgroundDetectionConfig.thresholdValue
2035 nZeroEncountered = 0
2036 if nFootprintTemp == 0:
2037 zeroFactor = min(0.98, 0.9 + 0.01*nZeroEncountered)
2038 starBackgroundDetectionConfig.thresholdValue = zeroFactor*currentThresh
2039 self.log.warning(
"No footprints detected. Decreasing threshold to %.2f and rerunning.",
2040 starBackgroundDetectionConfig.thresholdValue)
2041 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2042 config=starBackgroundDetectionConfig)
2044 tempDetections = starBackgroundDetectionTask.run(
2045 table=table, exposure=result.exposure, clearMask=
True)
2046 nFootprintTemp = len(tempDetections.sources)
2047 minFootprints = max(self.config.star_background_min_footprints,
2048 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2049 minFootprints = min(200, minFootprints)
2050 nZeroEncountered += 1
2051 if nFootprintTemp >= minFootprints:
2053 detected_mask_planes,
2055 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2056 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2057 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
2058 nIter, starBackgroundDetectionConfig.thresholdValue,
2059 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2060 nFootprintTemp, minFootprints)
2061 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2067 if nFootprintTemp > 0
and nFootprintTemp < minFootprints:
2070 if detected_fraction > maxDetFracForFinalBg
or nFootprintTemp <= minFootprints:
2071 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
2072 if nFootprintTemp < minFootprints
and detected_fraction > 0.9*maxDetFracForFinalBg:
2073 if nFootprintTemp == 1:
2074 starBackgroundDetectionConfig.thresholdValue = 1.4*currentThresh
2076 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
2078 if n_above_max_per_amp > 1:
2079 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
2080 if detected_fraction < minDetFracForFinalBg:
2081 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
2082 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2083 config=starBackgroundDetectionConfig)
2085 tempDetections = starBackgroundDetectionTask.run(
2086 table=table, exposure=result.exposure, clearMask=
True)
2087 result.exposure.mask |= dilatedMask
2088 nFootprintTemp = len(tempDetections.sources)
2089 minFootprints = max(self.config.star_background_min_footprints,
2090 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2091 minFootprints = min(200, minFootprints)
2094 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2095 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2096 "(max is %.3f; min is %.3f); nFootprint = %d (current min is %d)",
2097 nIter, starBackgroundDetectionConfig.thresholdValue,
2098 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2099 nFootprintTemp, minFootprints)
2100 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2102 n_amp = len(result.exposure.detector.getAmplifiers())
2103 if doCheckPerAmpDetFraction:
2104 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2106 detected_mask_planes, bad_mask_planes)
2108 if not no_zero_det_amps:
2109 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
2111 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
2112 and n_above_max_per_amp < int(0.75*n_amp)
2113 and no_zero_det_amps
2114 and nFootprintTemp >= minFootprints):
2115 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
2116 or detected_fraction < 0.85*maxDetFracForFinalBg):
2119 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
2120 self.log.debug(
"Number of amplifiers with detected fraction above the maximum "
2121 "(%.2f) excedes the maximum allowed (%d >= %d). Making a small "
2122 "tweak to the threshold (from %.2f to %.2f) and rerunning.",
2123 maxDetFracForFinalBg, n_above_max_per_amp, int(0.75*n_amp),
2124 currentThresh, starBackgroundDetectionConfig.thresholdValue)
2125 self.log.debug(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
2129 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
2130 "(highest per amp section = %.5f)",
2131 detected_fraction, highest_detected_fraction_per_amp)
2133 if detected_fraction > maxDetFracForFinalBg:
2134 result.exposure.mask = dilatedMask
2135 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
2136 "was too large in star_background_detection = %.3f (max = %.3f). "
2137 "Reverting to dilated mask from PSF detection.",
2138 detected_fraction, maxDetFracForFinalBg)
2139 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
2140 star_background = self.star_background.run(
2141 exposure=result.exposure,
2142 backgroundToPhotometricRatio=background_to_photometric_ratio,
2144 result.background.append(star_background[0])
2146 median_background = np.median(result.background.getImage().array)
2147 self.log.info(
"New initial median_background = %.2f", median_background)
2155 if detected_fraction < 0.5:
2156 dilatedMask = result.exposure.mask.clone()
2157 for maskName
in detected_mask_planes:
2159 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2160 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2161 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2162 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2164 detectedMask = dilatedMask.getMaskPlane(maskName)
2165 dilatedMask.clearMaskPlane(detectedMask)
2167 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2170 detected_mask_planes,
2172 result.exposure.mask = dilatedMask
2173 self.log.debug(
"detected_fraction_orig = %.3f; detected_fraction_dilated = %.3f",
2174 detected_fraction_orig, detected_fraction_dilated)
2176 bbox = result.exposure.getBBox()
2187 pedestalBackgroundConfig = lsst.meas.algorithms.SubtractBackgroundConfig()
2188 for maskName
in bgIgnoreMasksToAdd:
2189 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2190 and maskName
not in pedestalBackgroundConfig.ignoredPixelMask):
2191 pedestalBackgroundConfig.ignoredPixelMask += [maskName]
2192 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
2193 pedestalBackgroundConfig.approxOrderX = 0
2194 pedestalBackgroundConfig.binSize = min(32, int(math.ceil(max(bbox.width, bbox.height)/4.0)))
2195 self.log.info(
"Initial pedestal binSize = %d pixels", pedestalBackgroundConfig.binSize)
2196 cumulativePedestalLevel = 0.0
2199 relativeStoppingCriterion = 0.05
2203 absoluteStoppingCriterion = 0.5
2204 inPedestalIteration =
True
2205 while inPedestalIteration:
2206 cumulativePedestalPrev = cumulativePedestalLevel
2207 pedestalBackgroundTask = lsst.meas.algorithms.SubtractBackgroundTask(
2208 config=pedestalBackgroundConfig)
2209 pedestalBackgroundList = pedestalBackgroundTask.run(
2210 exposure=result.exposure,
2211 background=result.background,
2212 backgroundToPhotometricRatio=background_to_photometric_ratio,
2216 pedestalBackground = afwMath.BackgroundList()
2217 pedestalBackground.append(pedestalBackgroundList[-1])
2218 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
2219 cumulativePedestalLevel += pedestalBackgroundLevel
2220 absoluteDelta = abs(cumulativePedestalLevel - cumulativePedestalPrev)
2221 if cumulativePedestalPrev != 0.0:
2222 relativeDelta = abs(absoluteDelta/cumulativePedestalPrev)
2225 self.log.info(
"Subtracted pedestalBackgroundLevel = %.4f (cumulative = %.4f; "
2226 "relativeDelta = %.4f; absoluteDelta = %.3f)",
2227 pedestalBackgroundLevel, cumulativePedestalLevel,
2228 relativeDelta, absoluteDelta)
2230 if (relativeDelta < relativeStoppingCriterion
2231 or absoluteDelta < absoluteStoppingCriterion):
2233 inPedestalIteration =
False
2236 if pedestalBackgroundConfig.binSize*2 < 2*max(bbox.width, bbox.height):
2237 pedestalBackgroundConfig.binSize = int(pedestalBackgroundConfig.binSize*2)
2238 self.log.info(
"Increasing pedestal binSize to %d pixels and remeasuring.",
2239 pedestalBackgroundConfig.binSize)
2242 self.log.warning(
"Reached maximum bin size. Exiting pedestal loop without meeting "
2243 "the convergence criteria (relativeDelta <= %.2f or "
2244 "absoluteDelta <= %.2f).",
2245 relativeStoppingCriterion, absoluteStoppingCriterion)
2246 inPedestalIteration =
False
2247 self.log.info(
"Final subtracted cumulativePedestalBackgroundLevel = %.4f", cumulativePedestalLevel)
2250 mask = result.exposure.mask
2251 for mp
in detected_mask_planes:
2252 if mp
not in mask.getMaskPlaneDict():
2253 mask.addMaskPlane(mp)
2254 mask &= ~mask.getPlaneBitMask(detected_mask_planes)