Coverage for metadetect / lsst / skysub.py: 25%
100 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-24 01:39 -0800
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-24 01:39 -0800
1import lsst.afw.table as afw_table
2from lsst.meas.algorithms import (
3 SubtractBackgroundTask,
4 SubtractBackgroundConfig,
5 SourceDetectionTask,
6)
7from lsst.pex.config import Config, ConfigurableField, Field
8from lsst.pex.exceptions import RuntimeError as LSSTRuntimeError
9from lsst.pipe.base import Task, TaskError
11from .defaults import DEFAULT_THRESH
12from .measure import SourceDetectionConfig
13from . import util
16SourceDetectionTask.ConfigClass = SourceDetectionConfig
19class IterateDetectionSkySubConfig(Config):
20 niter = Field[int](
21 doc="Number of iterations",
22 default=2,
23 )
25 detect = ConfigurableField[SourceDetectionConfig](
26 doc="Detection config",
27 target=SourceDetectionTask
28 )
30 back = ConfigurableField[SubtractBackgroundConfig](
31 doc="Subtract background config",
32 target=SubtractBackgroundTask
33 )
35 def setDefaults(self):
36 super().setDefaults()
38 # detection
39 self.detect.reEstimateBackground = False
40 self.detect.thresholdValue = DEFAULT_THRESH
42 # background subtraction
43 bp_to_skip = util.get_stats_mask()
44 self.back = SubtractBackgroundConfig(ignoredPixelMask=bp_to_skip)
47class IterateDetectionSkySubTask(Task):
48 ConfigClass = IterateDetectionSkySubConfig
49 _DefaultName = "iterate_detection_and_skysub"
51 def __init__(self, *args, **kwargs):
52 super().__init__(*args, **kwargs)
53 self.makeSubtask("detect")
54 self.makeSubtask("back")
56 def run(self, exposure):
57 if self.config.niter < 1:
58 raise ValueError(f'niter {self.config.niter} is less than 1')
60 schema = afw_table.SourceTable.makeMinimalSchema()
61 table = afw_table.SourceTable.make(schema)
63 # keep a running sum of each sky that was subtracted
64 try:
65 sky_meas = 0.0
66 for i in range(self.config.niter):
67 self.back.run(exposure)
68 result = self.detect.run(table, exposure)
70 sky_meas += exposure.getMetadata()['BGMEAN']
72 meta = exposure.getMetadata()
74 # this is the overall sky we subtracted in all iterations
75 meta['BGMEAN'] = sky_meas
76 except LSSTRuntimeError as err:
77 err = str(err).replace('lsst::pex::exceptions::RuntimeError:', '')
78 self.detect.log.warn(err)
79 result = None
80 except TaskError as err:
81 err = str(err).replace('lsst.pipe.base.task.TaskError:', '')
82 self.detect.log.warn(err)
83 result = None
85 return result
88class SubtractSkyMbExpConfig(Config):
89 iterate_detection_and_skysub = ConfigurableField[IterateDetectionSkySubConfig](
90 doc="Iterate detection and sky subtraction config",
91 target=IterateDetectionSkySubTask
92 )
94 def setDefaults(self):
95 super().setDefaults()
97 self.iterate_detection_and_skysub.detect.thresholdValue = DEFAULT_THRESH
98 self.iterate_detection_and_skysub.niter = 2
101class SubtractSkyMbExpTask(Task):
102 ConfigClass = SubtractSkyMbExpConfig
103 _DefaultName = "subtract_sky_mbexp"
105 def __init__(self, *args, **kwargs):
106 super().__init__(*args, **kwargs)
107 self.makeSubtask("iterate_detection_and_skysub")
109 def run(self, mbexp):
110 for exp in mbexp:
111 self.iterate_detection_and_skysub.run(exposure=exp)
114def determine_and_subtract_sky(exp):
115 """
116 Determine and subtract the sky from the input exposure.
117 The exposure is modified.
119 Parameters
120 ----------
121 exp: Exposure
122 The exposure to be processed
123 """
125 bp_to_skip = util.get_stats_mask(exp)
126 back_config = SubtractBackgroundConfig(ignoredPixelMask=bp_to_skip)
127 back_task = SubtractBackgroundTask(config=back_config)
129 # returns background data, but we are ignoring it for now
130 background = back_task.run(exp)
131 return background
134def subtract_sky_mbexp(mbexp, thresh=DEFAULT_THRESH, config=None):
135 """
136 subtract sky
138 Parameters
139 ----------
140 mbexp: lsst.afw.image.MultibandExposure
141 The exposures to process
142 thresh: float
143 Threshold for detection
144 """
145 config_override = config if config is not None else {}
147 if thresh:
148 if 'iterate_detection_and_skysub' not in config_override:
149 config_override['iterate_detection_and_skysub'] = {}
150 if 'detect' not in config_override['iterate_detection_and_skysub']:
151 config_override['iterate_detection_and_skysub']['detect'] = {}
153 detect_override = config_override['iterate_detection_and_skysub']['detect']
154 detect_override['thresholdValue'] = thresh
156 config = SubtractSkyMbExpConfig()
157 config.setDefaults()
159 util.override_config(config, config_override)
161 config.freeze()
162 config.validate()
163 task = SubtractSkyMbExpTask(config=config)
164 task.run(mbexp=mbexp)
167def iterate_detection_and_skysub(
168 exposure, thresh, niter=2, config=None
169):
170 """
171 Iterate detection and sky subtraction
173 Parameters
174 ----------
175 exposure: Exposure
176 The exposure to process
177 thresh: float
178 threshold for detection
179 niter: int, optional
180 Number of iterations for detection and sky subtraction.
181 Must be >= 1. Default is 2 which is recommended.
183 Returns
184 -------
185 Result from running the detection task
186 """
187 if niter < 1:
188 raise ValueError(f'niter {niter} is less than 1')
190 config_override = config if config is not None else {}
192 # set threshold
193 if 'detect' not in config_override:
194 config_override['detect'] = {}
195 config_override['detect']['thresholdValue'] = thresh
197 if niter:
198 config_override['niter'] = niter
200 config = IterateDetectionSkySubConfig()
201 config.setDefaults()
203 util.override_config(config, config_override)
205 task = IterateDetectionSkySubTask(config=config)
206 result = task.run(exposure)
207 return result