|
32 | 32 | from lsst.ip.diffim.utils import (evaluateMaskFraction, computeDifferenceImageMetrics, |
33 | 33 | populate_sattle_visit_cache) |
34 | 34 | from lsst.meas.algorithms import SkyObjectsTask, SourceDetectionTask, SetPrimaryFlagsTask, MaskStreaksTask |
| 35 | +from lsst.meas.algorithms import FindGlintTrailsTask |
35 | 36 | from lsst.meas.base import ForcedMeasurementTask, ApplyApCorrTask, DetectorVisitIdGeneratorConfig |
36 | 37 | import lsst.meas.deblender |
37 | 38 | import lsst.meas.extensions.trailedSources # noqa: F401 |
@@ -138,13 +139,21 @@ class DetectAndMeasureConnections(pipeBase.PipelineTaskConnections, |
138 | 139 | dimensions=("instrument", "visit", "detector"), |
139 | 140 | name="{fakesType}{coaddName}Diff_streaks", |
140 | 141 | ) |
| 142 | + glintTrailInfo = pipeBase.connectionTypes.Output( |
| 143 | + doc='Dict of fit parameters for glint trails in the catalog.', |
| 144 | + storageClass="ArrowNumpyDict", |
| 145 | + dimensions=("instrument", "visit", "detector"), |
| 146 | + name="trailed_glints", |
| 147 | + ) |
141 | 148 |
|
142 | 149 | def __init__(self, *, config): |
143 | 150 | super().__init__(config=config) |
144 | 151 | if not (self.config.writeStreakInfo and self.config.doMaskStreaks): |
145 | 152 | self.outputs.remove("maskedStreaks") |
146 | 153 | if not (self.config.doSubtractBackground and self.config.doWriteBackground): |
147 | 154 | self.outputs.remove("differenceBackground") |
| 155 | + if not (self.config.writeGlintInfo): |
| 156 | + self.outputs.remove("glintTrailInfo") |
148 | 157 |
|
149 | 158 |
|
150 | 159 | class DetectAndMeasureConfig(pipeBase.PipelineTaskConfig, |
@@ -260,6 +269,15 @@ class DetectAndMeasureConfig(pipeBase.PipelineTaskConfig, |
260 | 269 | doc="Record the parameters of any detected streaks. For LSST, this should be turned off except for " |
261 | 270 | "development work." |
262 | 271 | ) |
| 272 | + findGlints = pexConfig.ConfigurableField( |
| 273 | + target=FindGlintTrailsTask, |
| 274 | + doc="Subtask for finding glint trails, usually caused by satellites or debris." |
| 275 | + ) |
| 276 | + writeGlintInfo = pexConfig.Field( |
| 277 | + dtype=bool, |
| 278 | + default=True, |
| 279 | + doc="Record the parameters of any detected glint trails." |
| 280 | + ) |
263 | 281 | setPrimaryFlags = pexConfig.ConfigurableField( |
264 | 282 | target=SetPrimaryFlagsTask, |
265 | 283 | doc="Task to add isPrimary and deblending-related flags to the catalog." |
@@ -455,6 +473,8 @@ def __init__(self, **kwargs): |
455 | 473 | if self.config.doMaskStreaks: |
456 | 474 | self.makeSubtask("maskStreaks") |
457 | 475 | self.makeSubtask("streakDetection") |
| 476 | + self.makeSubtask("findGlints") |
| 477 | + self.schema.addField("glint_trail", "Flag", "DiaSource is part of a glint trail.") |
458 | 478 |
|
459 | 479 | # To get the "merge_*" fields in the schema; have to re-initialize |
460 | 480 | # this later, once we have a peak schema post-detection. |
@@ -492,6 +512,7 @@ def runQuantum(self, butlerQC: pipeBase.QuantumContext, |
492 | 512 | measurementResults.subtractedMeasuredExposure, |
493 | 513 | measurementResults.diaSources, |
494 | 514 | measurementResults.maskedStreaks, |
| 515 | + measurementResults.glintTrailInfo, |
495 | 516 | log=self.log |
496 | 517 | ) |
497 | 518 | butlerQC.put(measurementResults, outputRefs) |
@@ -715,6 +736,13 @@ def processResults(self, science, matchedTemplate, difference, sources, idFactor |
715 | 736 | initialDiaSources = initialDiaSources.copy(deep=True) |
716 | 737 |
|
717 | 738 | self.measureDiaSources(initialDiaSources, science, difference, matchedTemplate) |
| 739 | + |
| 740 | + # Add a column for glint trail diaSources, but do not remove them |
| 741 | + initialDiaSources, trail_parameters = self._find_glint_trails(initialDiaSources) |
| 742 | + if self.config.writeGlintInfo: |
| 743 | + measurementResults.mergeItems(trail_parameters, 'glintTrailInfo') |
| 744 | + |
| 745 | + # Remove unphysical diaSources per config.badSourceFlags |
718 | 746 | diaSources = self._removeBadSources(initialDiaSources) |
719 | 747 |
|
720 | 748 | if self.config.run_sattle: |
@@ -835,6 +863,39 @@ def _removeBadSources(self, diaSources): |
835 | 863 | self.log.info("Removed %d unphysical sources.", nBadTotal) |
836 | 864 | return diaSources[selector].copy(deep=True) |
837 | 865 |
|
| 866 | + def _find_glint_trails(self, diaSources): |
| 867 | + """Define a new flag column for diaSources that are in a glint trail. |
| 868 | +
|
| 869 | + Parameters |
| 870 | + ---------- |
| 871 | + diaSources : `lsst.afw.table.SourceCatalog` |
| 872 | + The catalog of detected sources. |
| 873 | +
|
| 874 | + Returns |
| 875 | + ------- |
| 876 | + diaSources : `lsst.afw.table.SourceCatalog` |
| 877 | + The updated catalog of detected sources, with a new bool column |
| 878 | + called 'glint_trail' added. |
| 879 | +
|
| 880 | + trail_parameters : `dict` |
| 881 | + Parameters of all the trails that were found. |
| 882 | + """ |
| 883 | + trailed_glints = self.findGlints.run(diaSources) |
| 884 | + glint_mask = [True if id in trailed_glints.trailed_ids else False for id in diaSources['id']] |
| 885 | + diaSources['glint_trail'] = np.array(glint_mask) |
| 886 | + |
| 887 | + slopes = np.array([trail.slope for trail in trailed_glints.parameters]) |
| 888 | + intercepts = np.array([trail.intercept for trail in trailed_glints.parameters]) |
| 889 | + stderrs = np.array([trail.stderr for trail in trailed_glints.parameters]) |
| 890 | + lengths = np.array([trail.length for trail in trailed_glints.parameters]) |
| 891 | + angles = np.array([trail.angle for trail in trailed_glints.parameters]) |
| 892 | + parameters = {'slopes': slopes, 'intercepts': intercepts, 'stderrs': stderrs, 'lengths': lengths, |
| 893 | + 'angles': angles} |
| 894 | + |
| 895 | + trail_parameters = pipeBase.Struct(glintTrailInfo=parameters) |
| 896 | + |
| 897 | + return diaSources, trail_parameters |
| 898 | + |
838 | 899 | def addSkySources(self, diaSources, mask, seed, |
839 | 900 | subtask=None): |
840 | 901 | """Add sources in empty regions of the difference image |
|
0 commit comments