799 inputs = butlerQC.get(inputRefs)
800 exposures = inputs.pop(
"exposures")
802 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
804 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
805 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
806 refCats=inputs.pop(
"astrometry_ref_cat"),
807 name=self.config.connections.astrometry_ref_cat,
808 config=self.config.astrometry_ref_loader, log=self.log)
809 self.astrometry.setRefObjLoader(astrometry_loader)
811 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
812 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
813 refCats=inputs.pop(
"photometry_ref_cat"),
814 name=self.config.connections.photometry_ref_cat,
815 config=self.config.photometry_ref_loader, log=self.log)
816 self.photometry.match.setRefObjLoader(photometry_loader)
818 if self.config.doMaskDiffractionSpikes:
821 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
823 if self.config.do_illumination_correction:
824 background_flat = inputs.pop(
"background_flat")
825 illumination_correction = inputs.pop(
"illumination_correction")
827 background_flat =
None
828 illumination_correction =
None
831 if self.config.useButlerCamera:
832 if "camera_model" in inputs:
833 camera_model = inputs.pop(
"camera_model")
835 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
836 "astrometry fit will use the WCS already attached to the exposure.",
837 exposures[0].filter.bandLabel)
840 assert not inputs,
"runQuantum got more inputs than expected"
844 result = pipeBase.Struct(
846 stars_footprints=
None,
847 psf_stars_footprints=
None,
848 background_to_photometric_ratio=
None,
854 id_generator=id_generator,
855 background_flat=background_flat,
856 illumination_correction=illumination_correction,
857 camera_model=camera_model,
859 except pipeBase.AlgorithmError
as e:
860 error = pipeBase.AnnotatedPartialOutputsError.annotate(
864 result.psf_stars_footprints,
865 result.stars_footprints,
868 butlerQC.put(result, outputRefs)
871 butlerQC.put(result, outputRefs)
880 background_flat=None,
881 illumination_correction=None,
884 """Find stars and perform psf measurement, then do a deeper detection
885 and measurement and calibrate astrometry and photometry from that.
889 exposures : `lsst.afw.image.Exposure` or \
890 `list` [`lsst.afw.image.Exposure`]
891 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
892 Modified in-place during processing if only one is passed.
893 If two exposures are passed, treat them as snaps and combine
894 before doing further processing.
895 id_generator : `lsst.meas.base.IdGenerator`, optional
896 Object that generates source IDs and provides random seeds.
897 result : `lsst.pipe.base.Struct`, optional
898 Result struct that is modified to allow saving of partial outputs
899 for some failure conditions. If the task completes successfully,
900 this is also returned.
901 background_flat : `lsst.afw.image.Exposure`, optional
902 Background flat-field image.
903 illumination_correction : `lsst.afw.image.Exposure`, optional
904 Illumination correction image.
905 camera_model : `lsst.afw.cameraGeom.Camera`, optional
906 Camera to be used if constructing updated WCS.
910 result : `lsst.pipe.base.Struct`
911 Results as a struct with attributes:
914 Calibrated exposure, with pixels in nJy units.
915 (`lsst.afw.image.Exposure`)
917 Stars that were used to calibrate the exposure, with
918 calibrated fluxes and magnitudes.
919 (`astropy.table.Table`)
921 Footprints of stars that were used to calibrate the exposure.
922 (`lsst.afw.table.SourceCatalog`)
924 Stars that were used to determine the image PSF.
925 (`astropy.table.Table`)
926 ``psf_stars_footprints``
927 Footprints of stars that were used to determine the image PSF.
928 (`lsst.afw.table.SourceCatalog`)
930 Background that was fit to the exposure when detecting
931 ``stars``. (`lsst.afw.math.BackgroundList`)
932 ``applied_photo_calib``
933 Photometric calibration that was fit to the star catalog and
934 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
935 This is `None` if ``config.do_calibrate_pixels`` is `False`.
936 ``astrometry_matches``
937 Reference catalog stars matches used in the astrometric fit.
938 (`list` [`lsst.afw.table.ReferenceMatch`] or
939 `lsst.afw.table.BaseCatalog`).
940 ``photometry_matches``
941 Reference catalog stars matches used in the photometric fit.
942 (`list` [`lsst.afw.table.ReferenceMatch`] or
943 `lsst.afw.table.BaseCatalog`).
945 Copy of the mask plane of `exposure`.
946 (`lsst.afw.image.Mask`)
949 result = pipeBase.Struct()
950 if id_generator
is None:
951 id_generator = lsst.meas.base.IdGenerator()
953 result.exposure = self.snap_combine.run(exposures).exposure
954 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
956 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
959 if self.config.do_illumination_correction:
960 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
961 raise pipeBase.InvalidQuantumError(
962 "Cannot use do_illumination_correction with an image that has not had a flat applied",
966 if camera_model.get(result.exposure.detector.getId()):
967 self.log.info(
"Updating WCS with the provided camera model.")
971 "useButlerCamera=True, but detector %s is not available in the provided camera. The "
972 "astrometry fit will use the WCS already attached to the exposure.",
973 result.exposure.detector.getId())
975 result.background =
None
976 result.background_to_photometric_ratio =
None
977 summary_stat_catalog =
None
983 have_fit_astrometry =
False
984 have_fit_photometry =
False
989 illumination_correction,
992 result.psf_stars_footprints, _, adaptive_det_res_struct = self.
_compute_psf(
1001 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
1002 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
1004 n_sources=len(result.psf_stars_footprints),
1005 psf_shape_ixx=psf_shape.getIxx(),
1006 psf_shape_iyy=psf_shape.getIyy(),
1007 psf_shape_ixy=psf_shape.getIxy(),
1008 psf_size=psf_shape.getDeterminantRadius(),
1011 if result.background
is None:
1012 result.background = afwMath.BackgroundList()
1015 result.psf_stars = result.psf_stars_footprints.asAstropy()
1018 afwTable.updateSourceCoords(
1019 result.exposure.wcs,
1020 sourceList=result.psf_stars_footprints,
1021 include_covariance=self.config.do_include_astrometric_errors,
1024 result.exposure, result.psf_stars_footprints
1026 num_astrometry_matches = len(astrometry_matches)
1027 if "astrometry_matches" in self.config.optional_outputs:
1030 result.psf_stars = result.psf_stars_footprints.asAstropy()
1034 if self.config.doMaskDiffractionSpikes:
1035 self.diffractionSpikeMask.run(result.exposure)
1037 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
1038 if self.config.do_adaptive_threshold_detection:
1041 background_to_photometric_ratio=result.background_to_photometric_ratio,
1049 background_to_photometric_ratio=result.background_to_photometric_ratio,
1050 adaptive_det_res_struct=adaptive_det_res_struct,
1051 num_astrometry_matches=num_astrometry_matches,
1053 psf = result.exposure.getPsf()
1054 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1055 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1059 afwTable.updateSourceCoords(
1060 result.exposure.wcs,
1061 sourceList=result.stars_footprints,
1062 include_covariance=self.config.do_include_astrometric_errors,
1065 summary_stat_catalog = result.stars_footprints
1066 result.stars = result.stars_footprints.asAstropy()
1067 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1072 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1073 result.stars = result.stars_footprints.asAstropy()
1074 have_fit_astrometry =
True
1076 result.stars_footprints, photometry_matches, \
1077 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1078 have_fit_photometry =
True
1079 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1082 result.stars = result.stars_footprints.asAstropy()
1086 summary_stat_catalog = result.stars_footprints
1087 if "photometry_matches" in self.config.optional_outputs:
1090 if "mask" in self.config.optional_outputs:
1091 result.mask = result.exposure.mask.clone()
1092 except pipeBase.AlgorithmError:
1093 if not have_fit_psf:
1094 result.exposure.setPsf(
None)
1095 if not have_fit_astrometry:
1096 result.exposure.setWcs(
None)
1097 if not have_fit_photometry:
1098 result.exposure.setPhotoCalib(
None)
1105 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1108 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1113 bgIgnoredPixelMasks = lsst.meas.algorithms.SubtractBackgroundConfig().ignoredPixelMask.list()
1114 for maskName
in self.config.background_stats_ignored_pixel_masks:
1115 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1116 and maskName
not in bgIgnoredPixelMasks):
1117 bgIgnoredPixelMasks += [maskName]
1119 statsCtrl = afwMath.StatisticsControl()
1120 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(bgIgnoredPixelMasks))
1122 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.MEDIAN, statsCtrl)
1123 median_bg, _ = statObj.getResult(afwMath.MEDIAN)
1124 self.metadata[
"bg_subtracted_skyPixel_instFlux_median"] = median_bg
1126 statObj = afwMath.makeStatistics(result.exposure.getMaskedImage(), afwMath.STDEV, statsCtrl)
1127 stdev_bg, _ = statObj.getResult(afwMath.STDEV)
1128 self.metadata[
"bg_subtracted_skyPixel_instFlux_stdev"] = stdev_bg
1130 self.metadata[
"bg_subtracted_skySource_flux_median"] = (
1131 np.median(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1133 self.metadata[
"bg_subtracted_skySource_flux_stdev"] = (
1134 np.std(result.stars[result.stars[
'sky_source']][self.config.background_stats_flux_column])
1137 if self.config.do_calibrate_pixels:
1141 background_to_photometric_ratio=result.background_to_photometric_ratio,
1143 result.applied_photo_calib = photo_calib
1145 result.applied_photo_calib =
None
1149 if self.config.run_sattle:
1153 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1154 historical=self.config.sattle_historical)
1155 self.log.info(
'Successfully triggered load of sattle visit cache')
1156 except requests.exceptions.HTTPError:
1157 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1207 """Find bright sources detected on an exposure and fit a PSF model to
1208 them, repairing likely cosmic rays before detection.
1210 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1211 model does not include contributions from cosmic rays.
1215 result : `lsst.pipe.base.Struct`
1216 Result struct that is modified to allow saving of partial outputs
1217 for some failure conditions. Should contain at least the following
1220 - exposure : `lsst.afw.image.Exposure`
1221 Exposure to detect and measure bright stars on.
1222 - background : `lsst.afw.math.BackgroundList` | `None`
1223 Background that was fit to the exposure during detection.
1224 - background_to_photometric_ratio : `lsst.afw.image.Image` | `None`
1225 Image to convert photometric-flattened image to
1226 background-flattened image.
1227 id_generator : `lsst.meas.base.IdGenerator`
1228 Object that generates source IDs and provides random seeds.
1232 sources : `lsst.afw.table.SourceCatalog`
1233 Catalog of detected bright sources.
1234 cell_set : `lsst.afw.math.SpatialCellSet`
1235 PSF candidates returned by the psf determiner.
1236 adaptive_det_res_struct : `lsst.pipe.base.Struct`
1237 Result struct from the adaptive threshold detection.
1241 This method modifies the exposure, background and
1242 background_to_photometric_ratio attributes of the result struct
1245 def log_psf(msg, addToMetadata=False):
1246 """Log the parameters of the psf and background, with a prepended
1247 message. There is also the option to add the PSF sigma to the task
1253 Message to prepend the log info with.
1254 addToMetadata : `bool`, optional
1255 Whether to add the final psf sigma value to the task
1256 metadata (the default is False).
1258 position = result.exposure.psf.getAveragePosition()
1259 sigma = result.exposure.psf.computeShape(position).getDeterminantRadius()
1260 dimensions = result.exposure.psf.computeImage(position).getDimensions()
1261 if result.background
is not None:
1262 median_background = np.median(result.background.getImage().array)
1264 median_background = 0.0
1265 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1266 msg, sigma, dimensions, median_background)
1268 self.metadata[
"final_psf_sigma"] = sigma
1270 self.log.info(
"First pass detection with Gaussian PSF FWHM=%s pixels",
1271 self.config.install_simple_psf.fwhm)
1272 self.install_simple_psf.run(exposure=result.exposure)
1274 result.background = self.psf_subtract_background.run(
1275 exposure=result.exposure,
1276 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1278 log_psf(
"Initial PSF:")
1279 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1281 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1282 if not self.config.do_adaptive_threshold_detection:
1283 adaptive_det_res_struct =
None
1286 detections = self.psf_detection.run(
1288 exposure=result.exposure,
1289 background=result.background,
1290 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1293 initialThreshold = self.config.psf_detection.thresholdValue
1294 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1295 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1296 table, result.exposure,
1297 initialThreshold=initialThreshold,
1298 initialThresholdMultiplier=initialThresholdMultiplier,
1300 detections = adaptive_det_res_struct.detections
1302 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1303 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1304 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1305 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1306 self.psf_source_measurement.run(detections.sources, result.exposure)
1307 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1314 if not self.config.do_adaptive_threshold_detection:
1318 self.install_simple_psf.run(exposure=result.exposure)
1320 log_psf(
"Rerunning with simple PSF:")
1328 self.psf_repair.run(exposure=result.exposure, keepCRs=
True)
1331 detections = self.psf_detection.run(
1333 exposure=result.exposure,
1334 background=result.background,
1335 backgroundToPhotometricRatio=result.background_to_photometric_ratio,
1337 self.psf_source_measurement.run(detections.sources, result.exposure)
1338 psf_result = self.psf_measure_psf.run(exposure=result.exposure, sources=detections.sources)
1339 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1340 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1341 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1342 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1343 log_psf(
"Final PSF:", addToMetadata=
True)
1346 self.psf_repair.run(exposure=result.exposure)
1348 self.psf_source_measurement.run(detections.sources, result.exposure)
1352 return detections.sources, psf_result.cellSet, adaptive_det_res_struct
1384 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1385 adaptive_det_res_struct=None, num_astrometry_matches=None):
1386 """Detect stars on an exposure that has a PSF model, and measure their
1387 PSF, circular aperture, compensated gaussian fluxes.
1391 exposure : `lsst.afw.image.Exposure`
1392 Exposure to detect and measure stars on.
1393 background : `lsst.afw.math.BackgroundList`
1394 Background that was fit to the exposure during detection;
1395 modified in-place during subsequent detection.
1396 id_generator : `lsst.meas.base.IdGenerator`
1397 Object that generates source IDs and provides random seeds.
1398 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1399 Image to convert photometric-flattened image to
1400 background-flattened image.
1404 stars : `SourceCatalog`
1405 Sources that are very likely to be stars, with a limited set of
1406 measurements performed on them.
1409 id_generator.make_table_id_factory())
1411 maxAdaptiveDetIter = 8
1413 if adaptive_det_res_struct
is not None:
1414 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1415 adaptiveDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1416 adaptiveDetectionConfig.reEstimateBackground =
False
1417 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1419 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1421 adaptiveDetectionConfig.thresholdValue = max(
1422 self.config.star_detection.thresholdValue,
1423 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1425 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1426 "thresholdValue = %.2f and multiplier = %.1f",
1427 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1428 adaptiveDetectionConfig.includeThresholdMultiplier)
1429 adaptiveDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1430 config=adaptiveDetectionConfig
1432 detections = adaptiveDetectionTask.run(
1435 background=background,
1436 backgroundToPhotometricRatio=background_to_photometric_ratio,
1438 nFootprint = len(detections.sources)
1441 for src
in detections.sources:
1442 nPeakSrc = len(src.getFootprint().getPeaks())
1446 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1448 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1449 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1450 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1451 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1452 threshFactor = max(0.01, 1.5*threshFactor)
1453 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1454 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1458 threshFactor *= 0.75
1459 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1460 "factor to %.2f. and rerunning.", threshFactor)
1464 detections = self.star_detection.run(
1467 background=background,
1468 backgroundToPhotometricRatio=background_to_photometric_ratio,
1470 sources = detections.sources
1471 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1473 n_sky_sources = np.sum(sources[
"sky_source"])
1474 if (self.config.do_downsample_footprints
1475 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1476 if exposure.info.id
is None:
1477 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1480 seed = exposure.info.id & 0xFFFFFFFF
1482 gen = np.random.RandomState(seed)
1485 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1486 indices = gen.choice(
1488 size=self.config.downsample_max_footprints,
1491 skyIndices, = np.where(sources[
"sky_source"])
1492 indices = np.concatenate((indices, skyIndices))
1494 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1496 sel = np.zeros(len(sources), dtype=bool)
1498 sources = sources[sel]
1502 self.star_deblend.run(exposure=exposure, sources=sources)
1505 if not sources.isContiguous():
1506 sources = sources.copy(deep=
True)
1509 self.star_measurement.run(sources, exposure)
1510 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1511 self.metadata[
"failed_deblend_source_count"] = np.sum(
1512 ~sources[
"sky_source"] & sources[
"deblend_failed"]
1514 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1515 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1520 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1521 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1522 self.star_catalog_calculation.run(sources)
1523 self.star_set_primary_flags.run(sources)
1525 result = self.star_selector.run(sources)
1527 if not result.sourceCat.isContiguous():
1528 return result.sourceCat.copy(deep=
True)
1530 return result.sourceCat
1849 """Remeasure the exposure's background with iterative adaptive
1850 threshold detection.
1854 result : `lsst.pipe.base.Struct`
1855 The current state of the result Strut from the run method of
1856 CalibrateImageTask. Will be modified in place.
1857 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1858 Image to convert photometric-flattened image to
1859 background-flattened image.
1863 result : `lsst.pipe.base.Struct`
1864 The modified result Struct with the new background subtracted.
1869 backgroundOrig = result.background.clone()
1870 median_background_orig = np.median(backgroundOrig.getImage().array)
1871 self.log.info(
"Original median_background = %.2f", median_background_orig)
1872 result.exposure.image.array += result.background.getImage().array
1873 result.background = afwMath.BackgroundList()
1875 origMask = result.exposure.mask.clone()
1876 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
1877 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
1879 detected_mask_planes,
1881 minDetFracForFinalBg = 0.02
1882 maxDetFracForFinalBg = 0.93
1886 maxAdaptiveDetIter = 10
1887 for dilateIter
in range(maxAdaptiveDetIter):
1888 dilatedMask = origMask.clone()
1889 for maskName
in detected_mask_planes:
1891 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1892 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1893 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1894 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1896 detectedMask = dilatedMask.getMaskPlane(maskName)
1897 dilatedMask.clearMaskPlane(detectedMask)
1899 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1902 detected_mask_planes,
1904 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
1909 result.exposure.mask = dilatedMask
1910 self.log.warning(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
1911 detected_fraction_orig, detected_fraction_dilated)
1912 n_above_max_per_amp = -99
1913 highest_detected_fraction_per_amp = float(
"nan")
1916 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1918 detected_mask_planes, bad_mask_planes)
1919 self.log.warning(
"Dilated mask: n_above_max_per_amp = %d, "
1920 "highest_detected_fraction_per_amp = %.3f",
1921 n_above_max_per_amp, highest_detected_fraction_per_amp)
1923 bgIgnoreMasksToAdd = [
"SAT",
"SUSPECT",
"SPIKE"]
1924 detected_fraction = 1.0
1925 nFootprintTemp = 1e12
1926 starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1927 for maskName
in bgIgnoreMasksToAdd:
1928 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1929 and maskName
not in starBackgroundDetectionConfig.background.ignoredPixelMask):
1930 starBackgroundDetectionConfig.background.ignoredPixelMask += [maskName]
1931 starBackgroundDetectionConfig.doTempLocalBackground =
False
1932 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
1933 starBackgroundDetectionConfig.reEstimateBackground =
False
1934 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
1935 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background_orig)
1936 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
1938 n_above_max_per_amp = -99
1939 highest_detected_fraction_per_amp = float(
"nan")
1940 doCheckPerAmpDetFraction =
True
1942 minFootprints = self.config.star_background_min_footprints
1944 for nIter
in range(maxIter):
1945 currentThresh = starBackgroundDetectionConfig.thresholdValue
1946 nZeroEncountered = 0
1947 if nFootprintTemp == 0:
1948 zeroFactor = min(0.98, 0.9 + 0.01*nZeroEncountered)
1949 starBackgroundDetectionConfig.thresholdValue = zeroFactor*currentThresh
1950 self.log.warning(
"No footprints detected. Decreasing threshold to %.2f and rerunning.",
1951 starBackgroundDetectionConfig.thresholdValue)
1952 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1953 config=starBackgroundDetectionConfig)
1955 tempDetections = starBackgroundDetectionTask.run(
1956 table=table, exposure=result.exposure, clearMask=
True)
1957 nFootprintTemp = len(tempDetections.sources)
1958 minFootprints = max(self.config.star_background_min_footprints,
1959 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
1960 minFootprints = min(200, minFootprints)
1961 nZeroEncountered += 1
1962 if nFootprintTemp >= minFootprints:
1964 detected_mask_planes,
1966 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
1967 "DETECTED_NEGATIVE in star_background_detection = %.3f "
1968 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
1969 nIter, starBackgroundDetectionConfig.thresholdValue,
1970 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
1971 nFootprintTemp, minFootprints)
1972 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
1978 if nFootprintTemp > 0
and nFootprintTemp < minFootprints:
1981 if detected_fraction > maxDetFracForFinalBg
or nFootprintTemp <= minFootprints:
1982 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
1983 if nFootprintTemp < minFootprints
and detected_fraction > 0.9*maxDetFracForFinalBg:
1984 if nFootprintTemp == 1:
1985 starBackgroundDetectionConfig.thresholdValue = 1.4*currentThresh
1987 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
1989 if n_above_max_per_amp > 1:
1990 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
1991 if detected_fraction < minDetFracForFinalBg:
1992 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
1993 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1994 config=starBackgroundDetectionConfig)
1996 tempDetections = starBackgroundDetectionTask.run(
1997 table=table, exposure=result.exposure, clearMask=
True)
1998 result.exposure.mask |= dilatedMask
1999 nFootprintTemp = len(tempDetections.sources)
2000 minFootprints = max(self.config.star_background_min_footprints,
2001 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
2002 minFootprints = min(200, minFootprints)
2005 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
2006 "DETECTED_NEGATIVE in star_background_detection = %.3f "
2007 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
2008 nIter, starBackgroundDetectionConfig.thresholdValue,
2009 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
2010 nFootprintTemp, minFootprints)
2011 self.metadata[
'adaptive_threshold_value'] = starBackgroundDetectionConfig.thresholdValue
2013 n_amp = len(result.exposure.detector.getAmplifiers())
2014 if doCheckPerAmpDetFraction:
2015 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
2017 detected_mask_planes, bad_mask_planes)
2019 if not no_zero_det_amps:
2020 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
2022 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
2023 and n_above_max_per_amp < int(0.75*n_amp)
2024 and no_zero_det_amps
2025 and nFootprintTemp >= minFootprints):
2026 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
2027 or detected_fraction < 0.85*maxDetFracForFinalBg):
2030 self.log.warning(
"Making small tweak....")
2031 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
2032 self.log.warning(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
2036 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
2037 "(highest per amp section = %.5f)",
2038 detected_fraction, highest_detected_fraction_per_amp)
2040 if detected_fraction > maxDetFracForFinalBg:
2041 result.exposure.mask = dilatedMask
2042 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
2043 "was too large in star_background_detection = %.3f (max = %.3f). "
2044 "Reverting to dilated mask from PSF detection...",
2045 detected_fraction, maxDetFracForFinalBg)
2046 self.metadata[
'adaptive_threshold_value'] = float(
"nan")
2047 star_background = self.star_background.run(
2048 exposure=result.exposure,
2049 backgroundToPhotometricRatio=background_to_photometric_ratio,
2051 result.background.append(star_background[0])
2053 median_background = np.median(result.background.getImage().array)
2054 self.log.info(
"New initial median_background = %.2f", median_background)
2062 if detected_fraction < 0.5:
2063 dilatedMask = result.exposure.mask.clone()
2064 for maskName
in detected_mask_planes:
2066 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2067 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2068 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2069 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2071 detectedMask = dilatedMask.getMaskPlane(maskName)
2072 dilatedMask.clearMaskPlane(detectedMask)
2074 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2077 detected_mask_planes,
2079 result.exposure.mask = dilatedMask
2080 self.log.debug(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
2081 detected_fraction_orig, detected_fraction_dilated)
2083 bbox = result.exposure.getBBox()
2094 pedestalBackgroundConfig = lsst.meas.algorithms.SubtractBackgroundConfig()
2095 for maskName
in bgIgnoreMasksToAdd:
2096 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2097 and maskName
not in pedestalBackgroundConfig.ignoredPixelMask):
2098 pedestalBackgroundConfig.ignoredPixelMask += [maskName]
2099 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
2100 pedestalBackgroundConfig.approxOrderX = 0
2101 pedestalBackgroundConfig.binSize = min(32, int(math.ceil(max(bbox.width, bbox.height)/4.0)))
2102 self.log.info(
"Initial pedestal binSize = %d pixels", pedestalBackgroundConfig.binSize)
2103 cumulativePedestalLevel = 0.0
2106 relativeStoppingCriterion = 0.05
2110 absoluteStoppingCriterion = 0.5
2111 inPedestalIteration =
True
2112 while inPedestalIteration:
2113 cumulativePedestalPrev = cumulativePedestalLevel
2114 pedestalBackgroundTask = lsst.meas.algorithms.SubtractBackgroundTask(
2115 config=pedestalBackgroundConfig)
2116 pedestalBackgroundList = pedestalBackgroundTask.run(
2117 exposure=result.exposure,
2118 background=result.background,
2119 backgroundToPhotometricRatio=background_to_photometric_ratio,
2123 pedestalBackground = afwMath.BackgroundList()
2124 pedestalBackground.append(pedestalBackgroundList[-1])
2125 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
2126 cumulativePedestalLevel += pedestalBackgroundLevel
2127 absoluteDelta = abs(cumulativePedestalLevel - cumulativePedestalPrev)
2128 if cumulativePedestalPrev != 0.0:
2129 relativeDelta = abs(absoluteDelta/cumulativePedestalPrev)
2132 self.log.info(
"Subtracted pedestalBackgroundLevel = %.4f (cumulative = %.4f; "
2133 "relativeDelta = %.4f; absoluteDelta = %.3f)",
2134 pedestalBackgroundLevel, cumulativePedestalLevel,
2135 relativeDelta, absoluteDelta)
2137 if (relativeDelta < relativeStoppingCriterion
2138 or absoluteDelta < absoluteStoppingCriterion):
2140 inPedestalIteration =
False
2143 if pedestalBackgroundConfig.binSize*2 < 2*max(bbox.width, bbox.height):
2144 pedestalBackgroundConfig.binSize = int(pedestalBackgroundConfig.binSize*2)
2145 self.log.info(
"Increasing pedestal binSize to %d pixels and remeasuring.",
2146 pedestalBackgroundConfig.binSize)
2149 self.log.warning(
"Reached maximum bin size. Exiting pedestal loop without meeting "
2150 "the convergence criteria (relativeDelta <= %.2f or "
2151 "absoluteDelta <= %.2f).",
2152 relativeStoppingCriterion, absoluteStoppingCriterion)
2153 inPedestalIteration =
False
2154 self.log.info(
"Final subtracted cumulativePedestalBackgroundLevel = %.4f", cumulativePedestalLevel)
2157 mask = result.exposure.mask
2158 for mp
in detected_mask_planes:
2159 if mp
not in mask.getMaskPlaneDict():
2160 mask.addMaskPlane(mp)
2161 mask &= ~mask.getPlaneBitMask(detected_mask_planes)