792 inputs = butlerQC.get(inputRefs)
793 exposures = inputs.pop(
"exposures")
795 id_generator = self.config.id_generator.apply(butlerQC.quantum.dataId)
798 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.astrometry_ref_cat],
799 refCats=inputs.pop(
"astrometry_ref_cat"),
800 name=self.config.connections.astrometry_ref_cat,
801 config=self.config.astrometry_ref_loader, log=self.log)
802 self.astrometry.setRefObjLoader(astrometry_loader)
805 dataIds=[ref.datasetRef.dataId
for ref
in inputRefs.photometry_ref_cat],
806 refCats=inputs.pop(
"photometry_ref_cat"),
807 name=self.config.connections.photometry_ref_cat,
808 config=self.config.photometry_ref_loader, log=self.log)
809 self.photometry.match.setRefObjLoader(photometry_loader)
811 if self.config.doMaskDiffractionSpikes:
814 self.diffractionSpikeMask.setRefObjLoader(photometry_loader)
816 if self.config.do_illumination_correction:
817 background_flat = inputs.pop(
"background_flat")
818 illumination_correction = inputs.pop(
"illumination_correction")
820 background_flat =
None
821 illumination_correction =
None
824 if self.config.useButlerCamera:
825 if "camera_model" in inputs:
826 camera_model = inputs.pop(
"camera_model")
828 self.log.warning(
"useButlerCamera=True, but camera is not available for filter %s. The "
829 "astrometry fit will use the WCS already attached to the exposure.",
830 exposures[0].filter.bandLabel)
833 assert not inputs,
"runQuantum got more inputs than expected"
837 result = pipeBase.Struct(
839 stars_footprints=
None,
840 psf_stars_footprints=
None,
841 background_to_photometric_ratio=
None,
847 id_generator=id_generator,
848 background_flat=background_flat,
849 illumination_correction=illumination_correction,
850 camera_model=camera_model,
852 except pipeBase.AlgorithmError
as e:
853 error = pipeBase.AnnotatedPartialOutputsError.annotate(
857 result.psf_stars_footprints,
858 result.stars_footprints,
861 butlerQC.put(result, outputRefs)
864 butlerQC.put(result, outputRefs)
873 background_flat=None,
874 illumination_correction=None,
877 """Find stars and perform psf measurement, then do a deeper detection
878 and measurement and calibrate astrometry and photometry from that.
882 exposures : `lsst.afw.image.Exposure` or \
883 `list` [`lsst.afw.image.Exposure`]
884 Post-ISR exposure(s), with an initial WCS, VisitInfo, and Filter.
885 Modified in-place during processing if only one is passed.
886 If two exposures are passed, treat them as snaps and combine
887 before doing further processing.
888 id_generator : `lsst.meas.base.IdGenerator`, optional
889 Object that generates source IDs and provides random seeds.
890 result : `lsst.pipe.base.Struct`, optional
891 Result struct that is modified to allow saving of partial outputs
892 for some failure conditions. If the task completes successfully,
893 this is also returned.
894 background_flat : `lsst.afw.image.Exposure`, optional
895 Background flat-field image.
896 illumination_correction : `lsst.afw.image.Exposure`, optional
897 Illumination correction image.
898 camera_model : `lsst.afw.cameraGeom.Camera`, optional
899 Camera to be used if constructing updated WCS.
903 result : `lsst.pipe.base.Struct`
904 Results as a struct with attributes:
907 Calibrated exposure, with pixels in nJy units.
908 (`lsst.afw.image.Exposure`)
910 Stars that were used to calibrate the exposure, with
911 calibrated fluxes and magnitudes.
912 (`astropy.table.Table`)
914 Footprints of stars that were used to calibrate the exposure.
915 (`lsst.afw.table.SourceCatalog`)
917 Stars that were used to determine the image PSF.
918 (`astropy.table.Table`)
919 ``psf_stars_footprints``
920 Footprints of stars that were used to determine the image PSF.
921 (`lsst.afw.table.SourceCatalog`)
923 Background that was fit to the exposure when detecting
924 ``stars``. (`lsst.afw.math.BackgroundList`)
925 ``applied_photo_calib``
926 Photometric calibration that was fit to the star catalog and
927 applied to the exposure. (`lsst.afw.image.PhotoCalib`)
928 This is `None` if ``config.do_calibrate_pixels`` is `False`.
929 ``astrometry_matches``
930 Reference catalog stars matches used in the astrometric fit.
931 (`list` [`lsst.afw.table.ReferenceMatch`] or
932 `lsst.afw.table.BaseCatalog`).
933 ``photometry_matches``
934 Reference catalog stars matches used in the photometric fit.
935 (`list` [`lsst.afw.table.ReferenceMatch`] or
936 `lsst.afw.table.BaseCatalog`).
938 Copy of the mask plane of `exposure`.
939 (`lsst.afw.image.Mask`)
942 result = pipeBase.Struct()
943 if id_generator
is None:
946 result.exposure = self.snap_combine.run(exposures).exposure
947 self.log.info(
"Initial PhotoCalib: %s", result.exposure.getPhotoCalib())
949 result.exposure.metadata[
"LSST CALIB ILLUMCORR APPLIED"] =
False
952 if self.config.do_illumination_correction:
953 if not result.exposure.metadata.get(
"LSST ISR FLAT APPLIED",
False):
954 raise pipeBase.InvalidQuantumError(
955 "Cannot use do_illumination_correction with an image that has not had a flat applied",
959 if camera_model.get(result.exposure.detector.getId()):
960 self.log.info(
"Updating WCS with the provided camera model.")
964 "useButlerCamera=True, but detector %s is not available in the provided camera. The "
965 "astrometry fit will use the WCS already attached to the exposure.",
966 result.exposure.detector.getId())
968 result.background =
None
969 summary_stat_catalog =
None
975 have_fit_astrometry =
False
976 have_fit_photometry =
False
981 illumination_correction,
984 result.psf_stars_footprints, result.background, _, adaptive_det_res_struct = self.
_compute_psf(
987 background_to_photometric_ratio=result.background_to_photometric_ratio,
994 if result.psf_stars_footprints[
"slot_Centroid_flag"].all():
995 psf_shape = result.exposure.psf.computeShape(result.exposure.psf.getAveragePosition())
997 n_sources=len(result.psf_stars_footprints),
998 psf_shape_ixx=psf_shape.getIxx(),
999 psf_shape_iyy=psf_shape.getIyy(),
1000 psf_shape_ixy=psf_shape.getIxy(),
1001 psf_size=psf_shape.getDeterminantRadius(),
1004 if result.background
is None:
1008 result.psf_stars = result.psf_stars_footprints.asAstropy()
1012 result.exposure.wcs,
1013 sourceList=result.psf_stars_footprints,
1014 include_covariance=self.config.do_include_astrometric_errors,
1017 result.exposure, result.psf_stars_footprints
1019 num_astrometry_matches = len(astrometry_matches)
1020 self.metadata[
"astrometry_matches_count"] = num_astrometry_matches
1021 if "astrometry_matches" in self.config.optional_outputs:
1024 result.psf_stars = result.psf_stars_footprints.asAstropy()
1028 if self.config.doMaskDiffractionSpikes:
1029 self.diffractionSpikeMask.run(result.exposure)
1031 if self.config.do_adaptive_threshold_detection:
1034 background_to_photometric_ratio=result.background_to_photometric_ratio,
1042 background_to_photometric_ratio=result.background_to_photometric_ratio,
1043 adaptive_det_res_struct=adaptive_det_res_struct,
1044 num_astrometry_matches=num_astrometry_matches,
1046 psf = result.exposure.getPsf()
1047 psfSigma = psf.computeShape(result.exposure.getBBox().getCenter()).getDeterminantRadius()
1048 self.
_match_psf_stars(result.psf_stars_footprints, result.stars_footprints,
1053 result.exposure.wcs,
1054 sourceList=result.stars_footprints,
1055 include_covariance=self.config.do_include_astrometric_errors,
1058 summary_stat_catalog = result.stars_footprints
1059 result.stars = result.stars_footprints.asAstropy()
1060 self.metadata[
"star_count"] = np.sum(~result.stars[
"sky_source"])
1065 self.astrometry.check(result.exposure, result.stars_footprints, len(astrometry_matches))
1066 result.stars = result.stars_footprints.asAstropy()
1067 have_fit_astrometry =
True
1069 result.stars_footprints, photometry_matches, \
1070 photometry_meta, photo_calib = self.
_fit_photometry(result.exposure, result.stars_footprints)
1071 have_fit_photometry =
True
1072 self.metadata[
"photometry_matches_count"] = len(photometry_matches)
1075 result.stars = result.stars_footprints.asAstropy()
1079 summary_stat_catalog = result.stars_footprints
1080 if "photometry_matches" in self.config.optional_outputs:
1083 if "mask" in self.config.optional_outputs:
1084 result.mask = result.exposure.mask.clone()
1085 except pipeBase.AlgorithmError:
1086 if not have_fit_psf:
1087 result.exposure.setPsf(
None)
1088 if not have_fit_astrometry:
1089 result.exposure.setWcs(
None)
1090 if not have_fit_photometry:
1091 result.exposure.setPhotoCalib(
None)
1098 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1101 self.
_summarize(result.exposure, summary_stat_catalog, result.background)
1107 for maskName
in self.config.background_stats_ignored_pixel_masks:
1108 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1109 and maskName
not in bgIgnoredPixelMasks):
1110 bgIgnoredPixelMasks += [maskName]
1113 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(bgIgnoredPixelMasks))
1116 median_bg, _ = statObj.getResult(afwMath.MEDIAN)
1117 self.metadata[
"bg_subtracted_skyPixel_instFlux_median"] = median_bg
1120 stdev_bg, _ = statObj.getResult(afwMath.STDEV)
1121 self.metadata[
"bg_subtracted_skyPixel_instFlux_stdev"] = stdev_bg
1123 self.metadata[
"bg_subtracted_skySource_flux_median"] = (
1124 np.median(result.stars[result.stars[
'sky_source']][
'base_CircularApertureFlux_12_0_flux'])
1126 self.metadata[
"bg_subtracted_skySource_flux_stdev"] = (
1127 np.std(result.stars[result.stars[
'sky_source']][
'base_CircularApertureFlux_12_0_flux'])
1130 if self.config.do_calibrate_pixels:
1134 background_to_photometric_ratio=result.background_to_photometric_ratio,
1136 result.applied_photo_calib = photo_calib
1138 result.applied_photo_calib =
None
1142 if self.config.run_sattle:
1146 populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
1147 historical=self.config.sattle_historical)
1148 self.log.info(
'Successfully triggered load of sattle visit cache')
1149 except requests.exceptions.HTTPError:
1150 self.log.exception(
"Sattle visit cache update failed; continuing with image processing")
1199 def _compute_psf(self, exposure, id_generator, background_to_photometric_ratio=None):
1200 """Find bright sources detected on an exposure and fit a PSF model to
1201 them, repairing likely cosmic rays before detection.
1203 Repair, detect, measure, and compute PSF twice, to ensure the PSF
1204 model does not include contributions from cosmic rays.
1208 exposure : `lsst.afw.image.Exposure`
1209 Exposure to detect and measure bright stars on.
1210 id_generator : `lsst.meas.base.IdGenerator`
1211 Object that generates source IDs and provides random seeds.
1212 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1213 Image to convert photometric-flattened image to
1214 background-flattened image.
1218 sources : `lsst.afw.table.SourceCatalog`
1219 Catalog of detected bright sources.
1220 background : `lsst.afw.math.BackgroundList`
1221 Background that was fit to the exposure during detection.
1222 cell_set : `lsst.afw.math.SpatialCellSet`
1223 PSF candidates returned by the psf determiner.
1225 def log_psf(msg, addToMetadata=False):
1226 """Log the parameters of the psf and background, with a prepended
1227 message. There is also the option to add the PSF sigma to the task
1233 Message to prepend the log info with.
1234 addToMetadata : `bool`, optional
1235 Whether to add the final psf sigma value to the task
1236 metadata (the default is False).
1238 position = exposure.psf.getAveragePosition()
1239 sigma = exposure.psf.computeShape(position).getDeterminantRadius()
1240 dimensions = exposure.psf.computeImage(position).getDimensions()
1241 if background
is not None:
1242 median_background = np.median(background.getImage().array)
1244 median_background = 0.0
1245 self.log.info(
"%s sigma=%0.4f, dimensions=%s; median background=%0.2f",
1246 msg, sigma, dimensions, median_background)
1248 self.metadata[
"final_psf_sigma"] = sigma
1250 self.log.info(
"First pass detection with Guassian PSF FWHM=%s pixels",
1251 self.config.install_simple_psf.fwhm)
1252 self.install_simple_psf.run(exposure=exposure)
1254 background = self.psf_subtract_background.run(
1256 backgroundToPhotometricRatio=background_to_photometric_ratio,
1258 log_psf(
"Initial PSF:")
1259 self.psf_repair.run(exposure=exposure, keepCRs=
True)
1261 table = afwTable.SourceTable.make(self.
psf_schema, id_generator.make_table_id_factory())
1262 if not self.config.do_adaptive_threshold_detection:
1263 adaptive_det_res_struct =
None
1266 detections = self.psf_detection.run(
1269 background=background,
1270 backgroundToPhotometricRatio=background_to_photometric_ratio,
1273 initialThreshold = self.config.psf_detection.thresholdValue
1274 initialThresholdMultiplier = self.config.psf_detection.includeThresholdMultiplier
1275 adaptive_det_res_struct = self.psf_adaptive_threshold_detection.run(
1277 initialThreshold=initialThreshold,
1278 initialThresholdMultiplier=initialThresholdMultiplier,
1280 detections = adaptive_det_res_struct.detections
1282 self.metadata[
"initial_psf_positive_footprint_count"] = detections.numPos
1283 self.metadata[
"initial_psf_negative_footprint_count"] = detections.numNeg
1284 self.metadata[
"initial_psf_positive_peak_count"] = detections.numPosPeaks
1285 self.metadata[
"initial_psf_negative_peak_count"] = detections.numNegPeaks
1286 self.psf_source_measurement.run(detections.sources, exposure)
1287 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources)
1294 if not self.config.do_adaptive_threshold_detection:
1298 self.install_simple_psf.run(exposure=exposure)
1300 log_psf(
"Rerunning with simple PSF:")
1308 self.psf_repair.run(exposure=exposure, keepCRs=
True)
1311 detections = self.psf_detection.run(
1314 background=background,
1315 backgroundToPhotometricRatio=background_to_photometric_ratio,
1317 self.psf_source_measurement.run(detections.sources, exposure)
1318 psf_result = self.psf_measure_psf.run(exposure=exposure, sources=detections.sources)
1320 self.metadata[
"simple_psf_positive_footprint_count"] = detections.numPos
1321 self.metadata[
"simple_psf_negative_footprint_count"] = detections.numNeg
1322 self.metadata[
"simple_psf_positive_peak_count"] = detections.numPosPeaks
1323 self.metadata[
"simple_psf_negative_peak_count"] = detections.numNegPeaks
1324 log_psf(
"Final PSF:", addToMetadata=
True)
1327 self.psf_repair.run(exposure=exposure)
1329 self.psf_source_measurement.run(detections.sources, exposure)
1333 return detections.sources, background, psf_result.cellSet, adaptive_det_res_struct
1365 def _find_stars(self, exposure, background, id_generator, background_to_photometric_ratio=None,
1366 adaptive_det_res_struct=None, num_astrometry_matches=None):
1367 """Detect stars on an exposure that has a PSF model, and measure their
1368 PSF, circular aperture, compensated gaussian fluxes.
1372 exposure : `lsst.afw.image.Exposure`
1373 Exposure to detect and measure stars on.
1374 background : `lsst.afw.math.BackgroundList`
1375 Background that was fit to the exposure during detection;
1376 modified in-place during subsequent detection.
1377 id_generator : `lsst.meas.base.IdGenerator`
1378 Object that generates source IDs and provides random seeds.
1379 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1380 Image to convert photometric-flattened image to
1381 background-flattened image.
1385 stars : `SourceCatalog`
1386 Sources that are very likely to be stars, with a limited set of
1387 measurements performed on them.
1390 id_generator.make_table_id_factory())
1392 maxAdaptiveDetIter = 8
1394 if adaptive_det_res_struct
is not None:
1395 for adaptiveDetIter
in range(maxAdaptiveDetIter):
1397 adaptiveDetectionConfig.reEstimateBackground =
False
1398 adaptiveDetectionConfig.includeThresholdMultiplier = 2.0
1400 adaptive_det_res_struct.thresholdValue*adaptive_det_res_struct.includeThresholdMultiplier
1402 adaptiveDetectionConfig.thresholdValue = max(
1403 self.config.star_detection.thresholdValue,
1404 threshFactor*psfThreshold/adaptiveDetectionConfig.includeThresholdMultiplier
1406 self.log.info(
"Using adaptive threshold detection (nIter = %d) with "
1407 "thresholdValue = %.2f and multiplier = %.1f",
1408 adaptiveDetIter, adaptiveDetectionConfig.thresholdValue,
1409 adaptiveDetectionConfig.includeThresholdMultiplier)
1411 config=adaptiveDetectionConfig
1413 detections = adaptiveDetectionTask.run(
1416 background=background,
1417 backgroundToPhotometricRatio=background_to_photometric_ratio,
1419 nFootprint = len(detections.sources)
1422 for src
in detections.sources:
1423 nPeakSrc = len(src.getFootprint().getPeaks())
1427 minIsolated = min(400, max(3, 0.005*nPeak, 0.6*num_astrometry_matches))
1429 self.log.info(
"Adaptive threshold detection _find_stars nIter: %d; "
1430 "nPeak/nFootprint = %.2f (max is 800); nIsolated = %d (min is %.1f).",
1431 adaptiveDetIter, nPeak/nFootprint, nIsolated, minIsolated)
1432 if nPeak/nFootprint > 800
or nIsolated < minIsolated:
1433 threshFactor = max(0.01, 1.5*threshFactor)
1434 self.log.warning(
"nPeak/nFootprint = %.2f (max is 800); nIsolated = %d "
1435 "(min is %.1f).", nPeak/nFootprint, nIsolated, minIsolated)
1439 threshFactor *= 0.75
1440 self.log.warning(
"No footprints detected on image. Decreasing threshold "
1441 "factor to %.2f. and rerunning.", threshFactor)
1445 detections = self.star_detection.run(
1448 background=background,
1449 backgroundToPhotometricRatio=background_to_photometric_ratio,
1451 sources = detections.sources
1452 self.star_sky_sources.run(exposure.mask, id_generator.catalog_id, sources)
1454 n_sky_sources = np.sum(sources[
"sky_source"])
1455 if (self.config.do_downsample_footprints
1456 and (len(sources) - n_sky_sources) > self.config.downsample_max_footprints):
1457 if exposure.info.id
is None:
1458 self.log.warning(
"Exposure does not have a proper id; using 0 seed for downsample.")
1461 seed = exposure.info.id & 0xFFFFFFFF
1463 gen = np.random.RandomState(seed)
1466 indices = np.arange(len(sources))[~sources[
"sky_source"]]
1467 indices = gen.choice(
1469 size=self.config.downsample_max_footprints,
1472 skyIndices, = np.where(sources[
"sky_source"])
1473 indices = np.concatenate((indices, skyIndices))
1475 self.log.info(
"Downsampling from %d to %d non-sky-source footprints.", len(sources), len(indices))
1477 sel = np.zeros(len(sources), dtype=bool)
1479 sources = sources[sel]
1483 self.star_deblend.run(exposure=exposure, sources=sources)
1486 if not sources.isContiguous():
1487 sources = sources.copy(deep=
True)
1490 self.star_measurement.run(sources, exposure)
1491 self.metadata[
"post_deblend_source_count"] = np.sum(~sources[
"sky_source"])
1492 self.metadata[
"saturated_source_count"] = np.sum(sources[
"base_PixelFlags_flag_saturated"])
1493 self.metadata[
"bad_source_count"] = np.sum(sources[
"base_PixelFlags_flag_bad"])
1498 self.star_normalized_calibration_flux.run(exposure=exposure, catalog=sources)
1499 self.star_apply_aperture_correction.run(sources, exposure.apCorrMap)
1500 self.star_catalog_calculation.run(sources)
1501 self.star_set_primary_flags.run(sources)
1503 result = self.star_selector.run(sources)
1505 if not result.sourceCat.isContiguous():
1506 return result.sourceCat.copy(deep=
True)
1508 return result.sourceCat
1804 """Remeasure the exposure's background with iterative adaptive
1805 threshold detection.
1809 result : `lsst.pipe.base.Struct`
1810 The current state of the result Strut from the run method of
1811 CalibrateImageTask. Will be modified in place.
1812 background_to_photometric_ratio : `lsst.afw.image.Image`, optional
1813 Image to convert photometric-flattened image to
1814 background-flattened image.
1818 result : `lsst.pipe.base.Struct`
1819 The modified result Struct with the new background subtracted.
1824 backgroundOrig = result.background.clone()
1825 median_background_orig = np.median(backgroundOrig.getImage().array)
1826 self.log.info(
"Original median_background = %.2f", median_background_orig)
1827 result.exposure.image.array += result.background.getImage().array
1830 origMask = result.exposure.mask.clone()
1831 bad_mask_planes = [
"BAD",
"EDGE",
"NO_DATA"]
1832 detected_mask_planes = [
"DETECTED",
"DETECTED_NEGATIVE"]
1834 detected_mask_planes,
1836 minDetFracForFinalBg = 0.02
1837 maxDetFracForFinalBg = 0.93
1841 maxAdaptiveDetIter = 10
1842 for dilateIter
in range(maxAdaptiveDetIter):
1843 dilatedMask = origMask.clone()
1844 for maskName
in detected_mask_planes:
1846 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1847 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1848 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1849 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1851 detectedMask = dilatedMask.getMaskPlane(maskName)
1852 dilatedMask.clearMaskPlane(detectedMask)
1854 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1857 detected_mask_planes,
1859 if detected_fraction_dilated < maxDetFracForFinalBg
or nPixToDilate == 1:
1864 result.exposure.mask = dilatedMask
1865 self.log.warning(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
1866 detected_fraction_orig, detected_fraction_dilated)
1867 n_above_max_per_amp = -99
1868 highest_detected_fraction_per_amp = float(
"nan")
1871 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1873 detected_mask_planes, bad_mask_planes)
1874 self.log.warning(
"Dilated mask: n_above_max_per_amp = %d, "
1875 "highest_detected_fraction_per_amp = %.3f",
1876 n_above_max_per_amp, highest_detected_fraction_per_amp)
1878 bgIgnoreMasksToAdd = [
"SAT",
"SUSPECT",
"SPIKE"]
1879 detected_fraction = 1.0
1880 nFootprintTemp = 1e12
1882 for maskName
in bgIgnoreMasksToAdd:
1883 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
1884 and maskName
not in starBackgroundDetectionConfig.background.ignoredPixelMask):
1885 starBackgroundDetectionConfig.background.ignoredPixelMask += [maskName]
1886 starBackgroundDetectionConfig.doTempLocalBackground =
False
1887 starBackgroundDetectionConfig.nSigmaToGrow = 70.0
1888 starBackgroundDetectionConfig.reEstimateBackground =
False
1889 starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
1890 starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background_orig)
1891 starBackgroundDetectionConfig.thresholdType =
"pixel_stdev"
1893 n_above_max_per_amp = -99
1894 highest_detected_fraction_per_amp = float(
"nan")
1895 doCheckPerAmpDetFraction =
True
1897 minFootprints = self.config.star_background_min_footprints
1899 for nIter
in range(maxIter):
1900 currentThresh = starBackgroundDetectionConfig.thresholdValue
1901 nZeroEncountered = 0
1902 if nFootprintTemp == 0:
1903 zeroFactor = min(0.98, 0.9 + 0.01*nZeroEncountered)
1904 starBackgroundDetectionConfig.thresholdValue = zeroFactor*currentThresh
1905 self.log.warning(
"No footprints detected. Decreasing threshold to %.2f and rerunning.",
1906 starBackgroundDetectionConfig.thresholdValue)
1908 config=starBackgroundDetectionConfig)
1910 tempDetections = starBackgroundDetectionTask.run(
1911 table=table, exposure=result.exposure, clearMask=
True)
1912 nFootprintTemp = len(tempDetections.sources)
1913 minFootprints = max(self.config.star_background_min_footprints,
1914 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
1915 minFootprints = min(200, minFootprints)
1916 nZeroEncountered += 1
1917 if nFootprintTemp >= minFootprints:
1919 detected_mask_planes,
1921 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
1922 "DETECTED_NEGATIVE in star_background_detection = %.3f "
1923 "(max is %.3f; min is %.3f) nFootprint = %d (current min is %d)",
1924 nIter, starBackgroundDetectionConfig.thresholdValue,
1925 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
1926 nFootprintTemp, minFootprints)
1931 if nFootprintTemp > 0
and nFootprintTemp < minFootprints:
1934 if detected_fraction > maxDetFracForFinalBg
or nFootprintTemp <= minFootprints:
1935 starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
1936 if nFootprintTemp < minFootprints
and detected_fraction > 0.9*maxDetFracForFinalBg:
1937 if nFootprintTemp == 1:
1938 starBackgroundDetectionConfig.thresholdValue = 1.4*currentThresh
1940 starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
1942 if n_above_max_per_amp > 1:
1943 starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
1944 if detected_fraction < minDetFracForFinalBg:
1945 starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
1947 config=starBackgroundDetectionConfig)
1949 tempDetections = starBackgroundDetectionTask.run(
1950 table=table, exposure=result.exposure, clearMask=
True)
1951 result.exposure.mask |= dilatedMask
1952 nFootprintTemp = len(tempDetections.sources)
1953 minFootprints = max(self.config.star_background_min_footprints,
1954 int(self.config.star_background_peak_fraction*tempDetections.numPosPeaks))
1955 minFootprints = min(200, minFootprints)
1958 self.log.info(
"nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
1959 "DETECTED_NEGATIVE in star_background_detection = %.3f "
1960 "(max is %.3f; min is %.3f) nFooprint = %d (current min is %d)",
1961 nIter, starBackgroundDetectionConfig.thresholdValue,
1962 detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg,
1963 nFootprintTemp, minFootprints)
1965 n_amp = len(result.exposure.detector.getAmplifiers())
1966 if doCheckPerAmpDetFraction:
1967 n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1969 detected_mask_planes, bad_mask_planes)
1971 if not no_zero_det_amps:
1972 starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
1974 if (detected_fraction < maxDetFracForFinalBg
and detected_fraction > minDetFracForFinalBg
1975 and n_above_max_per_amp < int(0.75*n_amp)
1976 and no_zero_det_amps
1977 and nFootprintTemp >= minFootprints):
1978 if (n_above_max_per_amp < max(1, int(0.15*n_amp))
1979 or detected_fraction < 0.85*maxDetFracForFinalBg):
1982 self.log.warning(
"Making small tweak....")
1983 starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
1984 self.log.warning(
"n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
1988 self.log.info(
"Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
1989 "(highest per amp section = %.5f)",
1990 detected_fraction, highest_detected_fraction_per_amp)
1992 if detected_fraction > maxDetFracForFinalBg:
1993 result.exposure.mask = dilatedMask
1994 self.log.warning(
"Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
1995 "was too large in star_background_detection = %.3f (max = %.3f). "
1996 "Reverting to dilated mask from PSF detection...",
1997 detected_fraction, maxDetFracForFinalBg)
1998 star_background = self.star_background.run(
1999 exposure=result.exposure,
2000 backgroundToPhotometricRatio=background_to_photometric_ratio,
2002 result.background.append(star_background[0])
2004 median_background = np.median(result.background.getImage().array)
2005 self.log.info(
"New initial median_background = %.2f", median_background)
2013 if detected_fraction < 0.5:
2014 dilatedMask = result.exposure.mask.clone()
2015 for maskName
in detected_mask_planes:
2017 detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
2018 detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
2019 detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
2020 detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
2022 detectedMask = dilatedMask.getMaskPlane(maskName)
2023 dilatedMask.clearMaskPlane(detectedMask)
2025 detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
2028 detected_mask_planes,
2030 result.exposure.mask = dilatedMask
2031 self.log.debug(
"detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
2032 detected_fraction_orig, detected_fraction_dilated)
2035 for maskName
in bgIgnoreMasksToAdd:
2036 if (maskName
in result.exposure.mask.getMaskPlaneDict().keys()
2037 and maskName
not in pedestalBackgroundConfig.ignoredPixelMask):
2038 pedestalBackgroundConfig.ignoredPixelMask += [maskName]
2039 pedestalBackgroundConfig.statisticsProperty =
"MEDIAN"
2040 pedestalBackgroundConfig.approxOrderX = 0
2041 pedestalBackgroundConfig.binSize = 64
2043 pedestalBackgroundList = pedestalBackgroundTask.run(
2044 exposure=result.exposure,
2045 background=result.background,
2046 backgroundToPhotometricRatio=background_to_photometric_ratio,
2050 pedestalBackground.append(pedestalBackgroundList[1])
2051 pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
2052 self.log.info(
"Subtracted pedestalBackgroundLevel = %.4f", pedestalBackgroundLevel)
2055 mask = result.exposure.mask
2056 for mp
in detected_mask_planes:
2057 if mp
not in mask.getMaskPlaneDict():
2058 mask.addMaskPlane(mp)
2059 mask &= ~mask.getPlaneBitMask(detected_mask_planes)