688 def __init__(self, initial_stars_schema=None, **kwargs):
690 self.makeSubtask(
"snap_combine")
693 self.makeSubtask(
"install_simple_psf")
694 self.makeSubtask(
"psf_repair")
695 self.makeSubtask(
"psf_subtract_background")
700 doc=
"PSF max value.",
702 afwTable.CoordKey.addErrorFields(self.
psf_schema)
703 self.makeSubtask(
"psf_detection", schema=self.
psf_schema)
704 self.makeSubtask(
"psf_source_measurement", schema=self.
psf_schema)
705 self.makeSubtask(
"psf_adaptive_threshold_detection")
706 self.makeSubtask(
"psf_measure_psf", schema=self.
psf_schema)
707 self.makeSubtask(
"psf_normalized_calibration_flux", schema=self.
psf_schema)
709 self.makeSubtask(
"measure_aperture_correction", schema=self.
psf_schema)
710 self.makeSubtask(
"astrometry", schema=self.
psf_schema)
713 if initial_stars_schema
is None:
714 initial_stars_schema = afwTable.SourceTable.makeMinimalSchema()
718 self.
psf_fields = (
"calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved",
719 "calib_astrometry_used",
722 "apcorr_slot_CalibFlux_used",
"apcorr_base_GaussianFlux_used",
723 "apcorr_base_PsfFlux_used",)
726 initial_stars_schema.addField(item.getField())
727 id_type = self.
psf_schema[
"id"].asField().getTypeString()
728 psf_max_value_type = self.
psf_schema[
'psf_max_value'].asField().getTypeString()
729 initial_stars_schema.addField(
"psf_id",
731 doc=
"id of this source in psf_stars; 0 if there is no match.")
732 initial_stars_schema.addField(
"psf_max_value",
733 type=psf_max_value_type,
734 doc=
"Maximum value in the star image used to train PSF.")
736 afwTable.CoordKey.addErrorFields(initial_stars_schema)
737 self.makeSubtask(
"star_background")
738 self.makeSubtask(
"star_detection", schema=initial_stars_schema)
739 self.makeSubtask(
"star_sky_sources", schema=initial_stars_schema)
740 self.makeSubtask(
"star_deblend", schema=initial_stars_schema)
741 self.makeSubtask(
"star_measurement", schema=initial_stars_schema)
742 self.makeSubtask(
"star_normalized_calibration_flux", schema=initial_stars_schema)
744 self.makeSubtask(
"star_apply_aperture_correction", schema=initial_stars_schema)
745 self.makeSubtask(
"star_catalog_calculation", schema=initial_stars_schema)
746 self.makeSubtask(
"star_set_primary_flags", schema=initial_stars_schema, isSingleFrame=
True)
747 self.makeSubtask(
"star_selector")
748 self.makeSubtask(
"photometry", schema=initial_stars_schema)
749 if self.config.doMaskDiffractionSpikes:
750 self.makeSubtask(
"diffractionSpikeMask")
751 self.makeSubtask(
"compute_summary_stats")
760 dummy_photo_calib = afwImage.PhotoCalib(1.0, 0, bbox=
lsst.geom.Box2I())
762 afwTable.SourceCatalog(initial_stars_schema)
766 inputs = butlerQC.get(inputRefs)
767 exposures = inputs.pop(
"exposures")
769 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
771 astrometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
772 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
773 refCats=inputs.pop(
"astrometry_ref_cat"),
774 name=self.config.connections.astrometry_ref_cat,
775 config=self.config.astrometry_ref_loader, log=self.log)
776 self.astrometry.setRefObjLoader(astrometry_loader)
778 photometry_loader = lsst.meas.algorithms.ReferenceObjectLoader(
779 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
780 refCats=inputs.pop(
"photometry_ref_cat"),
781 name=self.config.connections.photometry_ref_cat,
782 config=self.config.photometry_ref_loader, log=self.log)
783 self.photometry.match.setRefObjLoader(photometry_loader)
785 if self.config.doMaskDiffractionSpikes:
788 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
790 if self.config.do_illumination_correction:
791 background_flat = inputs.pop(
"background_flat")
792 illumination_correction = inputs.pop(
"illumination_correction")
794 background_flat =
None
795 illumination_correction =
None
798 if self.config.useButlerCamera:
799 if "camera_model" in inputs:
800 camera_model = inputs.pop(
"camera_model")
802 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
803 "astrometry fit will use the WCS already attached to the exposure.",
804 exposures[0].filter.bandLabel)
807 assert not inputs,
"runQuantum got more inputs than expected"
811 result = pipeBase.Struct(
813 stars_footprints=
None,
814 psf_stars_footprints=
None,
815 background_to_photometric_ratio=
None,
821 id_generator=id_generator,
822 background_flat=background_flat,
823 illumination_correction=illumination_correction,
824 camera_model=camera_model,
826 except pipeBase.AlgorithmError
as e:
827 error = pipeBase.AnnotatedPartialOutputsError.annotate(
831 result.psf_stars_footprints,
832 result.stars_footprints,
835 butlerQC.put(result, outputRefs)
838 butlerQC.put(result, outputRefs)
847 background_flat=None,
848 illumination_correction=None,
851 """Find stars and perform psf measurement, then do a deeper detection
852 and measurement and calibrate astrometry and photometry from that.
856 exposures : `lsst.afw.image.Exposure` or \
857 `list` [`lsst.afw.image.Exposure`]
858 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
859 Modified in-place during processing if only one is passed.
860 If two exposures are passed, treat them as snaps and combine
861 before doing further processing.
862 id_generator : `lsst.meas.base.IdGenerator`, optional
863 Object that generates source IDs and provides random seeds.
864 result : `lsst.pipe.base.Struct`, optional
865 Result struct that is modified to allow saving of partial outputs
866 for some failure conditions. If the task completes successfully,
867 this is also returned.
868 background_flat : `lsst.afw.image.Exposure`, optional
869 Background flat-field image.
870 illumination_correction : `lsst.afw.image.Exposure`, optional
871 Illumination correction image.
872 camera_model : `lsst.afw.cameraGeom.Camera`, optional
873 Camera to be used if constructing updated WCS.
877 result : `lsst.pipe.base.Struct`
878 Results as a struct with attributes:
881 Calibrated exposure, with pixels in nJy units.
882 (`lsst.afw.image.Exposure`)
884 Stars that were used to calibrate the exposure, with
885 calibrated fluxes and magnitudes.
886 (`astropy.table.Table`)
888 Footprints of stars that were used to calibrate the exposure.
889 (`lsst.afw.table.SourceCatalog`)
891 Stars that were used to determine the image PSF.
892 (`astropy.table.Table`)
893 ``psf_stars_footprints``
894 Footprints of stars that were used to determine the image PSF.
895 (`lsst.afw.table.SourceCatalog`)
897 Background that was fit to the exposure when detecting
898 ``stars``. (`lsst.afw.math.BackgroundList`)
899 ``applied_photo_calib``
900 Photometric calibration that was fit to the star catalog and
901 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
902 This is `None` if ``config.do_calibrate_pixels`` is `False`.
903 ``astrometry_matches``
904 Reference catalog stars matches used in the astrometric fit.
905 (`list` [`lsst.afw.table.ReferenceMatch`] or
906 `lsst.afw.table.BaseCatalog`).
907 ``photometry_matches``
908 Reference catalog stars matches used in the photometric fit.
909 (`list` [`lsst.afw.table.ReferenceMatch`] or
910 `lsst.afw.table.BaseCatalog`).
912 Copy of the mask plane of `exposure`.
913 (`lsst.afw.image.Mask`)
916 result = pipeBase.Struct()
917 if id_generator
is None:
918 id_generator = lsst.meas.base.IdGenerator()
920 result.exposure = self.snap_combine.run(exposures).exposure
922 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
924 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
927 if self.config.do_illumination_correction:
928 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
929 raise pipeBase.InvalidQuantumError(
930 "Cannot use do_illumination_correction with an image that has not had a flat applied",
934 if camera_model.get(result.exposure.detector.getId()):
935 self.log.info(
"Updating WCS with the provided camera model.")
939 "useButlerCamera=True, but detector %s is not available in the provided camera. The "
940 "astrometry fit will use the WCS already attached to the exposure.",
941 result.exposure.detector.getId())
943 result.background =
None
944 summary_stat_catalog =
None
950 have_fit_astrometry =
False
951 have_fit_photometry =
False
956 illumination_correction,
959 result.psf_stars_footprints, result.background, _, adaptive_det_res_struct = self.
_compute_psf(
962 background_to_photometric_ratio=result.background_to_photometric_ratio,
969 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
970 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
972 n_sources=len(result.psf_stars_footprints),
973 psf_shape_ixx=psf_shape.getIxx(),
974 psf_shape_iyy=psf_shape.getIyy(),
975 psf_shape_ixy=psf_shape.getIxy(),
976 psf_size=psf_shape.getDeterminantRadius(),
979 if result.background
is None:
980 result.background = afwMath.BackgroundList()
983 result.psf_stars = result.psf_stars_footprints.asAstropy()
986 afwTable.updateSourceCoords(
988 sourceList=result.psf_stars_footprints,
989 include_covariance=self.config.do_include_astrometric_errors,
992 result.exposure, result.psf_stars_footprints
994 num_astrometry_matches = len(astrometry_matches)
995 self.metadata[
"astrometry_matches_count"] = num_astrometry_matches
996 if "astrometry_matches" in self.config.optional_outputs:
999 result.psf_stars = result.psf_stars_footprints.asAstropy()
1001 if self.config.do_adaptive_threshold_detection:
1004 background_to_photometric_ratio=result.background_to_photometric_ratio,
1012 background_to_photometric_ratio=result.background_to_photometric_ratio,
1013 adaptive_det_res_struct=adaptive_det_res_struct,
1014 num_astrometry_matches=num_astrometry_matches,
1016 psf = result.exposure.getPsf()
1017 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1018 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1022 afwTable.updateSourceCoords(
1023 result.exposure.wcs,
1024 sourceList=result.stars_footprints,
1025 include_covariance=self.config.do_include_astrometric_errors,
1028 summary_stat_catalog = result.stars_footprints
1029 result.stars = result.stars_footprints.asAstropy()
1030 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1035 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1036 result.stars = result.stars_footprints.asAstropy()
1037 have_fit_astrometry =
True
1039 result.stars_footprints, photometry_matches, \
1040 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1041 have_fit_photometry =
True
1042 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1045 result.stars = result.stars_footprints.asAstropy()
1049 summary_stat_catalog = result.stars_footprints
1050 if "photometry_matches" in self.config.optional_outputs:
1053 if self.config.doMaskDiffractionSpikes:
1054 self.diffractionSpikeMask.run(result.exposure)
1055 if "mask" in self.config.optional_outputs:
1056 result.mask = result.exposure.mask.clone()
1057 except pipeBase.AlgorithmError:
1058 if not have_fit_psf:
1059 result.exposure.setPsf(
None)
1060 if not have_fit_astrometry:
1061 result.exposure.setWcs(
None)
1062 if not have_fit_photometry:
1063 result.exposure.setPhotoCalib(
None)
1070 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1073 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1075 if self.config.do_calibrate_pixels:
1079 background_to_photometric_ratio=result.background_to_photometric_ratio,
1081 result.applied_photo_calib = photo_calib
1083 result.applied_photo_calib =
None
1085 if self.config.run_sattle:
1089 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1090 historical=self.config.sattle_historical)
1091 self.log.info(
'Successfully triggered load of sattle visit cache')
1092 except requests.exceptions.HTTPError:
1093 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1142 def _compute_psf(self, exposure, id_generator, background_to_photometric_ratio=None):
1143 """Find bright sources detected on an exposure and fit a PSF model to
1144 them, repairing likely cosmic rays before detection.
1146 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1147 model does not include contributions from cosmic rays.
1151 exposure : `lsst.afw.image.Exposure`
1152 Exposure to detect and measure bright stars on.
1153 id_generator : `lsst.meas.base.IdGenerator`
1154 Object that generates source IDs and provides random seeds.
1155 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1156 Image to convert photometric-flattened image to
1157 background-flattened image.
1161 sources : `lsst.afw.table.SourceCatalog`
1162 Catalog of detected bright sources.
1163 background : `lsst.afw.math.BackgroundList`
1164 Background that was fit to the exposure during detection.
1165 cell_set : `lsst.afw.math.SpatialCellSet`
1166 PSF candidates returned by the psf determiner.
1168 def log_psf(msg, addToMetadata=False):
1169 """Log the parameters of the psf and background, with a prepended
1170 message. There is also the option to add the PSF sigma to the task
1176 Message to prepend the log info with.
1177 addToMetadata : `bool`, optional
1178 Whether to add the final psf sigma value to the task
1179 metadata (the default is False).
1181 position = exposure.psf.getAveragePosition()
1182 sigma = exposure.psf.computeShape(position).getDeterminantRadius()
1183 dimensions = exposure.psf.computeImage(position).getDimensions()
1184 if background
is not None:
1185 median_background = np.median(background.getImage().array)
1187 median_background = 0.0
1188 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1189 msg, sigma, dimensions, median_background)
1191 self.metadata[
"final_psf_sigma"] = sigma
1193 self.log.info(
"First pass detection with Guassian PSF FWHM=%s pixels",
1194 self.config.install_simple_psf.fwhm)
1195 self.install_simple_psf.run(exposure=exposure)
1197 background = self.psf_subtract_background.run(
1199 backgroundToPhotometricRatio=background_to_photometric_ratio,
1201 log_psf(
"Initial PSF:")
1202 self.psf_repair.run(exposure=exposure, keepCRs=
True)
1204 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1205 if not self.config.do_adaptive_threshold_detection:
1206 adaptive_det_res_struct =
None
1209 detections = self.psf_detection.run(
1212 background=background,
1213 backgroundToPhotometricRatio=background_to_photometric_ratio,
1216 initialThreshold = self.config.psf_detection.thresholdValue
1217 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1218 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1220 initialThreshold=initialThreshold,
1221 initialThresholdMultiplier=initialThresholdMultiplier,
1223 detections = adaptive_det_res_struct.detections
1225 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1226 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1227 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1228 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1229 self.psf_source_measurement.run(detections.sources, exposure)
1230 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources)
1237 if not self.config.do_adaptive_threshold_detection:
1241 self.install_simple_psf.run(exposure=exposure)
1243 log_psf(
"Rerunning with simple PSF:")
1251 self.psf_repair.run(exposure=exposure, keepCRs=
True)
1254 detections = self.psf_detection.run(
1257 background=background,
1258 backgroundToPhotometricRatio=background_to_photometric_ratio,
1260 self.psf_source_measurement.run(detections.sources, exposure)
1261 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources)
1263 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1264 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1265 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1266 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1267 log_psf(
"Final PSF:", addToMetadata=
True)
1270 self.psf_repair.run(exposure=exposure)
1272 self.psf_source_measurement.run(detections.sources, exposure)
1276 return detections.sources, background, psf_result.cellSet, adaptive_det_res_struct
1308 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1309 adaptive_det_res_struct=None, num_astrometry_matches=None):
1310 """Detect stars on an exposure that has a PSF model, and measure their
1311 PSF, circular aperture, compensated gaussian fluxes.
1315 exposure : `lsst.afw.image.Exposure`
1316 Exposure to detect and measure stars on.
1317 background : `lsst.afw.math.BackgroundList`
1318 Background that was fit to the exposure during detection;
1319 modified in-place during subsequent detection.
1320 id_generator : `lsst.meas.base.IdGenerator`
1321 Object that generates source IDs and provides random seeds.
1322 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1323 Image to convert photometric-flattened image to
1324 background-flattened image.
1328 stars : `SourceCatalog`
1329 Sources that are very likely to be stars, with a limited set of
1330 measurements performed on them.
1333 id_generator.make_table_id_factory())
1335 maxAdaptiveDetIter = 8
1337 if adaptive_det_res_struct
is not None:
1338 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1339 adaptiveDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1340 adaptiveDetectionConfig.reEstimateBackground =
False
1341 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1343 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1345 adaptiveDetectionConfig.thresholdValue = max(
1346 self.config.star_detection.thresholdValue,
1347 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1349 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1350 "thresholdValue = %.2f and multiplier = %.1f",
1351 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1352 adaptiveDetectionConfig.includeThresholdMultiplier)
1353 adaptiveDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1354 config=adaptiveDetectionConfig
1356 detections = adaptiveDetectionTask.run(
1359 background=background,
1360 backgroundToPhotometricRatio=background_to_photometric_ratio,
1362 nFootprint = len(detections.sources)
1365 for src
in detections.sources:
1366 nPeakSrc = len(src.getFootprint().getPeaks())
1370 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1372 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1373 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1374 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1375 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1376 threshFactor = max(0.01, 1.5*threshFactor)
1377 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1378 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1382 threshFactor *= 0.75
1383 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1384 "factor to %.2f. and rerunning.", threshFactor)
1388 detections = self.star_detection.run(
1391 background=background,
1392 backgroundToPhotometricRatio=background_to_photometric_ratio,
1394 sources = detections.sources
1395 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1397 n_sky_sources = np.sum(sources[
"sky_source"])
1398 if (self.config.do_downsample_footprints
1399 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1400 if exposure.info.id
is None:
1401 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1404 seed = exposure.info.id & 0xFFFFFFFF
1406 gen = np.random.RandomState(seed)
1409 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1410 indices = gen.choice(
1412 size=self.config.downsample_max_footprints,
1415 skyIndices, = np.where(sources[
"sky_source"])
1416 indices = np.concatenate((indices, skyIndices))
1418 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1420 sel = np.zeros(len(sources), dtype=bool)
1422 sources = sources[sel]
1426 self.star_deblend.run(exposure=exposure, sources=sources)
1429 if not sources.isContiguous():
1430 sources = sources.copy(deep=
True)
1433 self.star_measurement.run(sources, exposure)
1434 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1435 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1436 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1441 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1442 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1443 self.star_catalog_calculation.run(sources)
1444 self.star_set_primary_flags.run(sources)
1446 result = self.star_selector.run(sources)
1448 if not result.sourceCat.isContiguous():
1449 return result.sourceCat.copy(deep=
True)
1451 return result.sourceCat
1454 """Match calibration stars to psf stars, to identify which were psf
1455 candidates, and which were used or reserved during psf measurement
1456 and the astrometric fit.
1460 psf_stars : `lsst.afw.table.SourceCatalog`
1461 PSF candidate stars that were sent to the psf determiner and
1462 used in the astrometric fit. Used to populate psf and astrometry
1463 related flag fields.
1464 stars : `lsst.afw.table.SourceCatalog`
1465 Stars that will be used for calibration; psf-related fields will
1466 be updated in-place.
1470 This code was adapted from CalibrateTask.copyIcSourceFields().
1472 control = afwTable.MatchControl()
1473 matchRadius = 3.0
if psfSigma
is None else max(3.0, psfSigma)
1475 control.findOnlyClosest =
False
1476 matches = afwTable.matchXy(psf_stars, stars, matchRadius, control)
1477 deblend_key = stars.schema[
"deblend_nChild"].asKey()
1478 matches = [m
for m
in matches
if m[1].get(deblend_key) == 0]
1485 for match_psf, match_stars, d
in matches:
1486 match = best.get(match_psf.getId())
1487 if match
is None or d <= match[2]:
1488 best[match_psf.getId()] = (match_psf, match_stars, d)
1489 matches = list(best.values())
1491 ids = np.array([(match_psf.getId(), match_stars.getId())
for match_psf, match_stars, d
in matches]).T
1493 if (n_matches := len(matches)) == 0:
1496 self.log.info(
"%d psf/astrometry stars out of %d matched %d calib stars",
1497 n_matches, len(psf_stars), len(stars))
1498 self.metadata[
"matched_psf_star_count"] = n_matches
1503 n_unique = len(set(m[1].getId()
for m
in matches))
1504 if n_unique != n_matches:
1505 self.log.warning(
"%d psf_stars matched only %d stars", n_matches, n_unique)
1508 idx_psf_stars = np.searchsorted(psf_stars[
"id"], ids[0])
1509 idx_stars = np.searchsorted(stars[
"id"], ids[1])
1511 result = np.zeros(len(stars), dtype=bool)
1512 result[idx_stars] = psf_stars[field][idx_psf_stars]
1513 stars[field] = result
1514 stars[
'psf_id'][idx_stars] = psf_stars[
'id'][idx_psf_stars]
1515 stars[
'psf_max_value'][idx_stars] = psf_stars[
'psf_max_value'][idx_psf_stars]
1747 """Remeasure the exposure's background with iterative adaptive
1748 threshold detection.
1752 result : `lsst.pipe.base.Struct`
1753 The current state of the result Strut from the run method of
1754 CalibrateImageTask. Will be modified in place.
1755 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1756 Image to convert photometric-flattened image to
1757 background-flattened image.
1761 result : `lsst.pipe.base.Struct`
1762 The modified result Struct with the new background subtracted.
1767 backgroundOrig = result.background.clone()
1768 median_background = np.median(backgroundOrig.getImage().array)
1769 self.log.warning(
"Original median_background = %.2f", median_background)
1770 result.exposure.image.array += result.background.getImage().array
1771 result.background = afwMath.BackgroundList()
1773 origMask = result.exposure.mask.clone()
1774 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
1775 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
1777 detected_mask_planes,
1779 minDetFracForFinalBg = 0.02
1780 maxDetFracForFinalBg = 0.93
1784 maxAdaptiveDetIter = 10
1785 for dilateIter
in range(maxAdaptiveDetIter):
1786 dilatedMask = origMask.clone()
1787 for maskName
in detected_mask_planes:
1789 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1790 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1791 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1792 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1794 detectedMask = dilatedMask.getMaskPlane(maskName)
1795 dilatedMask.clearMaskPlane(detectedMask)
1797 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1800 detected_mask_planes,
1802 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
1807 result.exposure.mask = dilatedMask
1808 self.log.warning(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
1809 detected_fraction_orig, detected_fraction_dilated)
1810 n_above_max_per_amp = -99
1811 highest_detected_fraction_per_amp = float(
"nan")
1814 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1816 detected_mask_planes, bad_mask_planes)
1817 self.log.warning(
"Dilated mask: n_above_max_per_amp = %d, "
1818 "highest_detected_fraction_per_amp = %.3f",
1819 n_above_max_per_amp, highest_detected_fraction_per_amp)
1821 detected_fraction = 1.0
1822 nFootprintTemp = 1e12
1823 starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1824 starBackgroundDetectionConfig.doTempLocalBackground =
False
1825 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
1826 starBackgroundDetectionConfig.reEstimateBackground =
False
1827 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
1828 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background)
1829 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
1831 n_above_max_per_amp = -99
1832 highest_detected_fraction_per_amp = float(
"nan")
1833 doCheckPerAmpDetFraction =
True
1836 for nIter
in range(maxIter):
1837 currentThresh = starBackgroundDetectionConfig.thresholdValue
1838 if detected_fraction > maxDetFracForFinalBg:
1839 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
1840 if nFootprintTemp < 3
and detected_fraction > 0.9*maxDetFracForFinalBg:
1841 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
1842 if n_above_max_per_amp > 1:
1843 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
1844 if detected_fraction < minDetFracForFinalBg:
1845 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
1846 starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1847 config=starBackgroundDetectionConfig)
1849 tempDetections = starBackgroundDetectionTask.run(
1850 table=table, exposure=result.exposure, clearMask=
True)
1851 result.exposure.mask |= dilatedMask
1852 nFootprintTemp = len(tempDetections.sources)
1855 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
1856 "DETECTED_NEGATIVE in star_background_detection = %.3f "
1857 "(max is %.3f; min is %.3f)",
1858 nIter, starBackgroundDetectionConfig.thresholdValue,
1859 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg)
1861 n_amp = len(result.exposure.detector.getAmplifiers())
1862 if doCheckPerAmpDetFraction:
1863 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1865 detected_mask_planes, bad_mask_planes)
1867 if not no_zero_det_amps:
1868 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
1870 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
1871 and n_above_max_per_amp < int(0.75*n_amp)
1872 and no_zero_det_amps):
1873 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
1874 or detected_fraction < 0.85*maxDetFracForFinalBg):
1877 self.log.warning(
"Making small tweak....")
1878 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
1879 self.log.warning(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
1881 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
1882 "(highest per amp section = %.5f)",
1883 detected_fraction, highest_detected_fraction_per_amp)
1885 if detected_fraction > maxDetFracForFinalBg:
1886 result.exposure.mask = dilatedMask
1887 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
1888 "was too large in star_background_detection = %.3f (max = %.3f). "
1889 "Reverting to dilated mask from PSF detection...",
1890 detected_fraction, maxDetFracForFinalBg)
1891 star_background = self.star_background.run(
1892 exposure=result.exposure,
1893 backgroundToPhotometricRatio=background_to_photometric_ratio,
1895 result.background.append(star_background[0])
1903 if detected_fraction < 0.5:
1904 dilatedMask = result.exposure.mask.clone()
1905 for maskName
in detected_mask_planes:
1907 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1908 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1909 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1910 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1912 detectedMask = dilatedMask.getMaskPlane(maskName)
1913 dilatedMask.clearMaskPlane(detectedMask)
1915 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1918 detected_mask_planes,
1920 result.exposure.mask = dilatedMask
1921 self.log.debug(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
1922 detected_fraction_orig, detected_fraction_dilated)
1924 pedestalBackgroundConfig = lsst.meas.algorithms.SubtractBackgroundConfig()
1925 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
1926 pedestalBackgroundConfig.approxOrderX = 0
1927 pedestalBackgroundConfig.binSize = 64
1928 pedestalBackgroundTask = lsst.meas.algorithms.SubtractBackgroundTask(config=pedestalBackgroundConfig)
1929 pedestalBackgroundList = pedestalBackgroundTask.run(
1930 exposure=result.exposure,
1931 background=result.background,
1932 backgroundToPhotometricRatio=background_to_photometric_ratio,
1935 pedestalBackground = afwMath.BackgroundList()
1936 pedestalBackground.append(pedestalBackgroundList[1])
1937 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
1938 self.log.warning(
"Subtracted pedestalBackgroundLevel = %.4f", pedestalBackgroundLevel)
1941 mask = result.exposure.mask
1942 for mp
in detected_mask_planes:
1943 if mp
not in mask.getMaskPlaneDict():
1944 mask.addMaskPlane(mp)
1945 mask &= ~mask.getPlaneBitMask(detected_mask_planes)