800 inputs = butlerQC.get(inputRefs)
801 exposures = inputs.pop(
"exposures")
803 exposure_record = inputRefs.exposures[0].dataId.records[
"exposure"]
805 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
807 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
808 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
809 refCats=inputs.pop(
"astrometry_ref_cat"),
810 name=self.config.connections.astrometry_ref_cat,
811 config=self.config.astrometry_ref_loader, log=self.log)
812 self.astrometry.setRefObjLoader(astrometry_loader)
814 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
815 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
816 refCats=inputs.pop(
"photometry_ref_cat"),
817 name=self.config.connections.photometry_ref_cat,
818 config=self.config.photometry_ref_loader, log=self.log)
819 self.photometry.match.setRefObjLoader(photometry_loader)
821 if self.config.doMaskDiffractionSpikes:
824 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
826 if self.config.do_illumination_correction:
827 background_flat = inputs.pop(
"background_flat")
828 illumination_correction = inputs.pop(
"illumination_correction")
830 background_flat =
None
831 illumination_correction =
None
834 if self.config.useButlerCamera:
835 if "camera_model" in inputs:
836 camera_model = inputs.pop(
"camera_model")
838 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
839 "astrometry fit will use the WCS already attached to the exposure.",
840 exposures[0].filter.bandLabel)
843 assert not inputs,
"runQuantum got more inputs than expected"
847 result = pipeBase.Struct(
849 stars_footprints=
None,
850 psf_stars_footprints=
None,
851 background_to_photometric_ratio=
None,
857 id_generator=id_generator,
858 background_flat=background_flat,
859 illumination_correction=illumination_correction,
860 camera_model=camera_model,
861 exposure_record=exposure_record,
863 except pipeBase.AlgorithmError
as e:
864 error = pipeBase.AnnotatedPartialOutputsError.annotate(
868 result.psf_stars_footprints,
869 result.stars_footprints,
872 butlerQC.put(result, outputRefs)
875 butlerQC.put(result, outputRefs)
884 background_flat=None,
885 illumination_correction=None,
887 exposure_record=None,
889 """Find stars and perform psf measurement, then do a deeper detection
890 and measurement and calibrate astrometry and photometry from that.
894 exposures : `lsst.afw.image.Exposure` or \
895 `list` [`lsst.afw.image.Exposure`]
896 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
897 Modified in-place during processing if only one is passed.
898 If two exposures are passed, treat them as snaps and combine
899 before doing further processing.
900 id_generator : `lsst.meas.base.IdGenerator`, optional
901 Object that generates source IDs and provides random seeds.
902 result : `lsst.pipe.base.Struct`, optional
903 Result struct that is modified to allow saving of partial outputs
904 for some failure conditions. If the task completes successfully,
905 this is also returned.
906 background_flat : `lsst.afw.image.Exposure`, optional
907 Background flat-field image.
908 illumination_correction : `lsst.afw.image.Exposure`, optional
909 Illumination correction image.
910 camera_model : `lsst.afw.cameraGeom.Camera`, optional
911 Camera to be used if constructing updated WCS.
912 exposure_record : `lsst.daf.butler.DimensionRecord`, optional
913 Butler metadata for the ``exposure`` dimension. Used if
914 constructing an updated WCS instead of the boresight and
915 orientation angle from the visit info.
919 result : `lsst.pipe.base.Struct`
920 Results as a struct with attributes:
923 Calibrated exposure, with pixels in nJy units.
924 (`lsst.afw.image.Exposure`)
926 Stars that were used to calibrate the exposure, with
927 calibrated fluxes and magnitudes.
928 (`astropy.table.Table`)
930 Footprints of stars that were used to calibrate the exposure.
931 (`lsst.afw.table.SourceCatalog`)
933 Stars that were used to determine the image PSF.
934 (`astropy.table.Table`)
935 ``psf_stars_footprints``
936 Footprints of stars that were used to determine the image PSF.
937 (`lsst.afw.table.SourceCatalog`)
939 Background that was fit to the exposure when detecting
940 ``stars``. (`lsst.afw.math.BackgroundList`)
941 ``applied_photo_calib``
942 Photometric calibration that was fit to the star catalog and
943 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
944 This is `None` if ``config.do_calibrate_pixels`` is `False`.
945 ``astrometry_matches``
946 Reference catalog stars matches used in the astrometric fit.
947 (`list` [`lsst.afw.table.ReferenceMatch`] or
948 `lsst.afw.table.BaseCatalog`).
949 ``photometry_matches``
950 Reference catalog stars matches used in the photometric fit.
951 (`list` [`lsst.afw.table.ReferenceMatch`] or
952 `lsst.afw.table.BaseCatalog`).
954 Copy of the mask plane of `exposure`.
955 (`lsst.afw.image.Mask`)
958 result = pipeBase.Struct()
959 if id_generator
is None:
960 id_generator = lsst.meas.base.IdGenerator()
962 result.exposure = self.snap_combine.run(exposures).exposure
963 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
965 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
968 if self.config.do_illumination_correction:
969 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
970 raise pipeBase.InvalidQuantumError(
971 "Cannot use do_illumination_correction with an image that has not had a flat applied",
975 if camera_model.get(result.exposure.detector.getId()):
976 self.log.info(
"Updating WCS with the provided camera model.")
980 "useButlerCamera=True, but detector %s is not available in the provided camera. The "
981 "astrometry fit will use the WCS already attached to the exposure.",
982 result.exposure.detector.getId())
984 result.background =
None
985 result.background_to_photometric_ratio =
None
986 summary_stat_catalog =
None
992 have_fit_astrometry =
False
993 have_fit_photometry =
False
998 illumination_correction,
1001 result.psf_stars_footprints, _, adaptive_det_res_struct = self.
_compute_psf(
1010 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
1011 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
1013 n_sources=len(result.psf_stars_footprints),
1014 psf_shape_ixx=psf_shape.getIxx(),
1015 psf_shape_iyy=psf_shape.getIyy(),
1016 psf_shape_ixy=psf_shape.getIxy(),
1017 psf_size=psf_shape.getDeterminantRadius(),
1020 if result.background
is None:
1021 result.background = afwMath.BackgroundList()
1024 result.psf_stars = result.psf_stars_footprints.asAstropy()
1027 afwTable.updateSourceCoords(
1028 result.exposure.wcs,
1029 sourceList=result.psf_stars_footprints,
1030 include_covariance=self.config.do_include_astrometric_errors,
1033 result.exposure, result.psf_stars_footprints
1035 num_astrometry_matches = len(astrometry_matches)
1036 if "astrometry_matches" in self.config.optional_outputs:
1039 result.psf_stars = result.psf_stars_footprints.asAstropy()
1043 if self.config.doMaskDiffractionSpikes:
1044 self.diffractionSpikeMask.run(result.exposure)
1046 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
1047 if self.config.do_adaptive_threshold_detection:
1050 background_to_photometric_ratio=result.background_to_photometric_ratio,
1058 background_to_photometric_ratio=result.background_to_photometric_ratio,
1059 adaptive_det_res_struct=adaptive_det_res_struct,
1060 num_astrometry_matches=num_astrometry_matches,
1062 psf = result.exposure.getPsf()
1063 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1064 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1068 afwTable.updateSourceCoords(
1069 result.exposure.wcs,
1070 sourceList=result.stars_footprints,
1071 include_covariance=self.config.do_include_astrometric_errors,
1074 summary_stat_catalog = result.stars_footprints
1075 result.stars = result.stars_footprints.asAstropy()
1076 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1081 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1082 result.stars = result.stars_footprints.asAstropy()
1083 have_fit_astrometry =
True
1085 result.stars_footprints, photometry_matches, \
1086 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1087 have_fit_photometry =
True
1088 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1091 result.stars = result.stars_footprints.asAstropy()
1095 summary_stat_catalog = result.stars_footprints
1096 if "photometry_matches" in self.config.optional_outputs:
1099 if "mask" in self.config.optional_outputs:
1100 result.mask = result.exposure.mask.clone()
1101 except pipeBase.AlgorithmError:
1102 if not have_fit_psf:
1103 result.exposure.setPsf(
None)
1104 if not have_fit_astrometry:
1105 result.exposure.setWcs(
None)
1106 if not have_fit_photometry:
1107 result.exposure.setPhotoCalib(
None)
1114 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1117 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1122 bgIgnoredPixelMasks = lsst.meas.algorithms.SubtractBackgroundConfig().ignoredPixelMask.list()
1123 for maskName
in self.config.background_stats_ignored_pixel_masks:
1124 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1125 and maskName
not in bgIgnoredPixelMasks):
1126 bgIgnoredPixelMasks += [maskName]
1128 statsCtrl = afwMath.StatisticsControl()
1129 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(bgIgnoredPixelMasks))
1131 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.MEDIAN, statsCtrl)
1132 median_bg, _ = statObj.getResult(afwMath.MEDIAN)
1133 self.metadata[
"bg_subtracted_skyPixel_instFlux_median"] = median_bg
1135 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.STDEV, statsCtrl)
1136 stdev_bg, _ = statObj.getResult(afwMath.STDEV)
1137 self.metadata[
"bg_subtracted_skyPixel_instFlux_stdev"] = stdev_bg
1139 self.metadata[
"bg_subtracted_skySource_flux_median"] = (
1140 np.median(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1142 self.metadata[
"bg_subtracted_skySource_flux_stdev"] = (
1143 np.std(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1146 if self.config.do_calibrate_pixels:
1150 background_to_photometric_ratio=result.background_to_photometric_ratio,
1152 result.applied_photo_calib = photo_calib
1154 result.applied_photo_calib =
None
1158 if self.config.run_sattle:
1162 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1163 historical=self.config.sattle_historical)
1164 self.log.info(
'Successfully triggered load of sattle visit cache')
1165 except requests.exceptions.HTTPError:
1166 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1216 """Find bright sources detected on an exposure and fit a PSF model to
1217 them, repairing likely cosmic rays before detection.
1219 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1220 model does not include contributions from cosmic rays.
1224 result : `lsst.pipe.base.Struct`
1225 Result struct that is modified to allow saving of partial outputs
1226 for some failure conditions. Should contain at least the following
1229 - exposure : `lsst.afw.image.Exposure`
1230 Exposure to detect and measure bright stars on.
1231 - background : `lsst.afw.math.BackgroundList` | `None`
1232 Background that was fit to the exposure during detection.
1233 - background_to_photometric_ratio : `lsst.afw.image.Image` | `None`
1234 Image to convert photometric-flattened image to
1235 background-flattened image.
1236 id_generator : `lsst.meas.base.IdGenerator`
1237 Object that generates source IDs and provides random seeds.
1241 sources : `lsst.afw.table.SourceCatalog`
1242 Catalog of detected bright sources.
1243 cell_set : `lsst.afw.math.SpatialCellSet`
1244 PSF candidates returned by the psf determiner.
1245 adaptive_det_res_struct : `lsst.pipe.base.Struct`
1246 Result struct from the adaptive threshold detection.
1250 This method modifies the exposure, background and
1251 background_to_photometric_ratio attributes of the result struct
1254 def log_psf(msg, addToMetadata=False):
1255 """Log the parameters of the psf and background, with a prepended
1256 message. There is also the option to add the PSF sigma to the task
1262 Message to prepend the log info with.
1263 addToMetadata : `bool`, optional
1264 Whether to add the final psf sigma value to the task
1265 metadata (the default is False).
1267 position = result.exposure.psf.getAveragePosition()
1268 sigma = result.exposure.psf.computeShape(position).getDeterminantRadius()
1269 dimensions = result.exposure.psf.computeImage(position).getDimensions()
1270 if result.background
is not None:
1271 median_background = np.median(result.background.getImage().array)
1273 median_background = 0.0
1274 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1275 msg, sigma, dimensions, median_background)
1277 self.metadata[
"final_psf_sigma"] = sigma
1279 self.log.info(
"First pass detection with Gaussian PSF FWHM=%s pixels",
1280 self.config.install_simple_psf.fwhm)
1281 self.install_simple_psf.run(exposure=result.exposure)
1283 result.background = self.psf_subtract_background.run(
1284 exposure=result.exposure,
1285 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1287 log_psf(
"Initial PSF:")
1288 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1290 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1291 if not self.config.do_adaptive_threshold_detection:
1292 adaptive_det_res_struct =
None
1295 detections = self.psf_detection.run(
1297 exposure=result.exposure,
1298 background=result.background,
1299 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1302 initialThreshold = self.config.psf_detection.thresholdValue
1303 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1304 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1305 table, result.exposure,
1306 initialThreshold=initialThreshold,
1307 initialThresholdMultiplier=initialThresholdMultiplier,
1309 detections = adaptive_det_res_struct.detections
1311 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1312 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1313 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1314 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1315 self.psf_source_measurement.run(detections.sources, result.exposure)
1316 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1323 if not self.config.do_adaptive_threshold_detection:
1327 self.install_simple_psf.run(exposure=result.exposure)
1329 log_psf(
"Rerunning with simple PSF:")
1337 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1340 detections = self.psf_detection.run(
1342 exposure=result.exposure,
1343 background=result.background,
1344 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1346 self.psf_source_measurement.run(detections.sources, result.exposure)
1347 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1348 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1349 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1350 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1351 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1352 log_psf(
"Final PSF:", addToMetadata=
True)
1355 self.psf_repair.run(exposure=result.exposure)
1357 self.psf_source_measurement.run(detections.sources, result.exposure)
1361 return detections.sources, psf_result.cellSet, adaptive_det_res_struct
1393 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1394 adaptive_det_res_struct=None, num_astrometry_matches=None):
1395 """Detect stars on an exposure that has a PSF model, and measure their
1396 PSF, circular aperture, compensated gaussian fluxes.
1400 exposure : `lsst.afw.image.Exposure`
1401 Exposure to detect and measure stars on.
1402 background : `lsst.afw.math.BackgroundList`
1403 Background that was fit to the exposure during detection;
1404 modified in-place during subsequent detection.
1405 id_generator : `lsst.meas.base.IdGenerator`
1406 Object that generates source IDs and provides random seeds.
1407 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1408 Image to convert photometric-flattened image to
1409 background-flattened image.
1413 stars : `SourceCatalog`
1414 Sources that are very likely to be stars, with a limited set of
1415 measurements performed on them.
1418 id_generator.make_table_id_factory())
1420 maxAdaptiveDetIter = 8
1422 if adaptive_det_res_struct
is not None:
1423 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1424 adaptiveDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1425 adaptiveDetectionConfig.reEstimateBackground =
False
1426 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1428 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1430 adaptiveDetectionConfig.thresholdValue = max(
1431 self.config.star_detection.thresholdValue,
1432 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1434 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1435 "thresholdValue = %.2f and multiplier = %.1f",
1436 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1437 adaptiveDetectionConfig.includeThresholdMultiplier)
1438 adaptiveDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1439 config=adaptiveDetectionConfig
1441 detections = adaptiveDetectionTask.run(
1444 background=background,
1445 backgroundToPhotometricRatio=background_to_photometric_ratio,
1447 nFootprint = len(detections.sources)
1450 for src
in detections.sources:
1451 nPeakSrc = len(src.getFootprint().getPeaks())
1455 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1457 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1458 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1459 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1460 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1461 threshFactor = max(0.01, 1.5*threshFactor)
1462 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1463 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1467 threshFactor *= 0.75
1468 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1469 "factor to %.2f. and rerunning.", threshFactor)
1473 detections = self.star_detection.run(
1476 background=background,
1477 backgroundToPhotometricRatio=background_to_photometric_ratio,
1479 sources = detections.sources
1480 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1482 n_sky_sources = np.sum(sources[
"sky_source"])
1483 if (self.config.do_downsample_footprints
1484 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1485 if exposure.info.id
is None:
1486 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1489 seed = exposure.info.id & 0xFFFFFFFF
1491 gen = np.random.RandomState(seed)
1494 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1495 indices = gen.choice(
1497 size=self.config.downsample_max_footprints,
1500 skyIndices, = np.where(sources[
"sky_source"])
1501 indices = np.concatenate((indices, skyIndices))
1503 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1505 sel = np.zeros(len(sources), dtype=bool)
1507 sources = sources[sel]
1511 self.star_deblend.run(exposure=exposure, sources=sources)
1514 if not sources.isContiguous():
1515 sources = sources.copy(deep=
True)
1518 self.star_measurement.run(sources, exposure)
1519 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1520 self.metadata[
"failed_deblend_source_count"] = np.sum(
1521 ~sources[
"sky_source"] & sources[
"deblend_failed"]
1523 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1524 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1529 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1530 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1531 self.star_catalog_calculation.run(sources)
1532 self.star_set_primary_flags.run(sources)
1534 result = self.star_selector.run(sources)
1536 if not result.sourceCat.isContiguous():
1537 return result.sourceCat.copy(deep=
True)
1539 return result.sourceCat
1874 """Remeasure the exposure's background with iterative adaptive
1875 threshold detection.
1879 result : `lsst.pipe.base.Struct`
1880 The current state of the result Strut from the run method of
1881 CalibrateImageTask. Will be modified in place.
1882 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1883 Image to convert photometric-flattened image to
1884 background-flattened image.
1888 result : `lsst.pipe.base.Struct`
1889 The modified result Struct with the new background subtracted.
1894 backgroundOrig = result.background.clone()
1895 median_background_orig = np.median(backgroundOrig.getImage().array)
1896 self.log.info(
"Original median_background = %.2f", median_background_orig)
1897 result.exposure.image.array += result.background.getImage().array
1898 result.background = afwMath.BackgroundList()
1900 origMask = result.exposure.mask.clone()
1901 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
1902 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
1904 detected_mask_planes,
1906 minDetFracForFinalBg = 0.02
1907 maxDetFracForFinalBg = 0.93
1911 maxAdaptiveDetIter = 10
1912 for dilateIter
in range(maxAdaptiveDetIter):
1913 dilatedMask = origMask.clone()
1914 for maskName
in detected_mask_planes:
1916 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1917 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1918 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1919 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1921 detectedMask = dilatedMask.getMaskPlane(maskName)
1922 dilatedMask.clearMaskPlane(detectedMask)
1924 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1927 detected_mask_planes,
1929 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
1934 result.exposure.mask = dilatedMask
1935 self.log.warning(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
1936 detected_fraction_orig, detected_fraction_dilated)
1937 n_above_max_per_amp = -99
1938 highest_detected_fraction_per_amp = float(
"nan")
1941 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1943 detected_mask_planes, bad_mask_planes)
1944 self.log.warning(
"Dilated mask: n_above_max_per_amp = %d, "
1945 "highest_detected_fraction_per_amp = %.3f",
1946 n_above_max_per_amp, highest_detected_fraction_per_amp)
1948 bgIgnoreMasksToAdd = [
"SAT",
"SUSPECT",
"SPIKE"]
1949 detected_fraction = 1.0
1950 nFootprintTemp = 1e12
1951 starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1952 for maskName
in bgIgnoreMasksToAdd:
1953 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1954 and maskName
not in starBackgroundDetectionConfig.background.ignoredPixelMask):
1955 starBackgroundDetectionConfig.background.ignoredPixelMask += [maskName]
1956 starBackgroundDetectionConfig.doTempLocalBackground =
False
1957 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
1958 starBackgroundDetectionConfig.reEstimateBackground =
False
1959 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
1960 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background_orig)
1961 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
1963 n_above_max_per_amp = -99
1964 highest_detected_fraction_per_amp = float(
"nan")
1965 doCheckPerAmpDetFraction =
True
1967 minFootprints = self.config.star_background_min_footprints
1969 for nIter
in range(maxIter):
1970 currentThresh = starBackgroundDetectionConfig.thresholdValue
1971 nZeroEncountered = 0
1972 if nFootprintTemp == 0:
1973 zeroFactor = min(0.98, 0.9 + 0.01*nZeroEncountered)
1974 starBackgroundDetectionConfig.thresholdValue = zeroFactor*currentThresh
1975 self.log.warning(
"No footprints detected. Decreasing threshold to %.2f and rerunning.",
1976 starBackgroundDetectionConfig.thresholdValue)
1977 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1978 config=starBackgroundDetectionConfig)
1980 tempDetections = starBackgroundDetectionTask.run(
1981 table=table, exposure=result.exposure, clearMask=
True)
1982 nFootprintTemp = len(tempDetections.sources)
1983 minFootprints = max(self.config.star_background_min_footprints,
1984 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
1985 minFootprints = min(200, minFootprints)
1986 nZeroEncountered += 1
1987 if nFootprintTemp >= minFootprints:
1989 detected_mask_planes,
1991 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
1992 "DETECTED_NEGATIVE in star_background_detection = %.3f "
1993 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
1994 nIter, starBackgroundDetectionConfig.thresholdValue,
1995 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
1996 nFootprintTemp, minFootprints)
1997 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2003 if nFootprintTemp > 0
and nFootprintTemp < minFootprints:
2006 if detected_fraction > maxDetFracForFinalBg
or nFootprintTemp <= minFootprints:
2007 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
2008 if nFootprintTemp < minFootprints
and detected_fraction > 0.9*maxDetFracForFinalBg:
2009 if nFootprintTemp == 1:
2010 starBackgroundDetectionConfig.thresholdValue = 1.4*currentThresh
2012 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
2014 if n_above_max_per_amp > 1:
2015 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
2016 if detected_fraction < minDetFracForFinalBg:
2017 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
2018 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
2019 config=starBackgroundDetectionConfig)
2021 tempDetections = starBackgroundDetectionTask.run(
2022 table=table, exposure=result.exposure, clearMask=
True)
2023 result.exposure.mask |= dilatedMask
2024 nFootprintTemp = len(tempDetections.sources)
2025 minFootprints = max(self.config.star_background_min_footprints,
2026 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2027 minFootprints = min(200, minFootprints)
2030 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2031 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2032 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
2033 nIter, starBackgroundDetectionConfig.thresholdValue,
2034 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2035 nFootprintTemp, minFootprints)
2036 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2038 n_amp = len(result.exposure.detector.getAmplifiers())
2039 if doCheckPerAmpDetFraction:
2040 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2042 detected_mask_planes, bad_mask_planes)
2044 if not no_zero_det_amps:
2045 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
2047 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
2048 and n_above_max_per_amp < int(0.75*n_amp)
2049 and no_zero_det_amps
2050 and nFootprintTemp >= minFootprints):
2051 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
2052 or detected_fraction < 0.85*maxDetFracForFinalBg):
2055 self.log.warning(
"Making small tweak....")
2056 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
2057 self.log.warning(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
2061 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
2062 "(highest per amp section = %.5f)",
2063 detected_fraction, highest_detected_fraction_per_amp)
2065 if detected_fraction > maxDetFracForFinalBg:
2066 result.exposure.mask = dilatedMask
2067 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
2068 "was too large in star_background_detection = %.3f (max = %.3f). "
2069 "Reverting to dilated mask from PSF detection...",
2070 detected_fraction, maxDetFracForFinalBg)
2071 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
2072 star_background = self.star_background.run(
2073 exposure=result.exposure,
2074 backgroundToPhotometricRatio=background_to_photometric_ratio,
2076 result.background.append(star_background[0])
2078 median_background = np.median(result.background.getImage().array)
2079 self.log.info(
"New initial median_background = %.2f", median_background)
2087 if detected_fraction < 0.5:
2088 dilatedMask = result.exposure.mask.clone()
2089 for maskName
in detected_mask_planes:
2091 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2092 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2093 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2094 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2096 detectedMask = dilatedMask.getMaskPlane(maskName)
2097 dilatedMask.clearMaskPlane(detectedMask)
2099 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2102 detected_mask_planes,
2104 result.exposure.mask = dilatedMask
2105 self.log.debug(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
2106 detected_fraction_orig, detected_fraction_dilated)
2108 bbox = result.exposure.getBBox()
2119 pedestalBackgroundConfig = lsst.meas.algorithms.SubtractBackgroundConfig()
2120 for maskName
in bgIgnoreMasksToAdd:
2121 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2122 and maskName
not in pedestalBackgroundConfig.ignoredPixelMask):
2123 pedestalBackgroundConfig.ignoredPixelMask += [maskName]
2124 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
2125 pedestalBackgroundConfig.approxOrderX = 0
2126 pedestalBackgroundConfig.binSize = min(32, int(math.ceil(max(bbox.width, bbox.height)/4.0)))
2127 self.log.info(
"Initial pedestal binSize = %d pixels", pedestalBackgroundConfig.binSize)
2128 cumulativePedestalLevel = 0.0
2131 relativeStoppingCriterion = 0.05
2135 absoluteStoppingCriterion = 0.5
2136 inPedestalIteration =
True
2137 while inPedestalIteration:
2138 cumulativePedestalPrev = cumulativePedestalLevel
2139 pedestalBackgroundTask = lsst.meas.algorithms.SubtractBackgroundTask(
2140 config=pedestalBackgroundConfig)
2141 pedestalBackgroundList = pedestalBackgroundTask.run(
2142 exposure=result.exposure,
2143 background=result.background,
2144 backgroundToPhotometricRatio=background_to_photometric_ratio,
2148 pedestalBackground = afwMath.BackgroundList()
2149 pedestalBackground.append(pedestalBackgroundList[-1])
2150 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
2151 cumulativePedestalLevel += pedestalBackgroundLevel
2152 absoluteDelta = abs(cumulativePedestalLevel - cumulativePedestalPrev)
2153 if cumulativePedestalPrev != 0.0:
2154 relativeDelta = abs(absoluteDelta/cumulativePedestalPrev)
2157 self.log.info(
"Subtracted pedestalBackgroundLevel = %.4f (cumulative = %.4f; "
2158 "relativeDelta = %.4f; absoluteDelta = %.3f)",
2159 pedestalBackgroundLevel, cumulativePedestalLevel,
2160 relativeDelta, absoluteDelta)
2162 if (relativeDelta < relativeStoppingCriterion
2163 or absoluteDelta < absoluteStoppingCriterion):
2165 inPedestalIteration =
False
2168 if pedestalBackgroundConfig.binSize*2 < 2*max(bbox.width, bbox.height):
2169 pedestalBackgroundConfig.binSize = int(pedestalBackgroundConfig.binSize*2)
2170 self.log.info(
"Increasing pedestal binSize to %d pixels and remeasuring.",
2171 pedestalBackgroundConfig.binSize)
2174 self.log.warning(
"Reached maximum bin size. Exiting pedestal loop without meeting "
2175 "the convergence criteria (relativeDelta <= %.2f or "
2176 "absoluteDelta <= %.2f).",
2177 relativeStoppingCriterion, absoluteStoppingCriterion)
2178 inPedestalIteration =
False
2179 self.log.info(
"Final subtracted cumulativePedestalBackgroundLevel = %.4f", cumulativePedestalLevel)
2182 mask = result.exposure.mask
2183 for mp
in detected_mask_planes:
2184 if mp
not in mask.getMaskPlaneDict():
2185 mask.addMaskPlane(mp)
2186 mask &= ~mask.getPlaneBitMask(detected_mask_planes)