Coverage for metadetect / lsst / skysub.py: 25%

100 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-18 01:28 -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 

10 

11from .defaults import DEFAULT_THRESH 

12from .measure import SourceDetectionConfig 

13from . import util 

14 

15 

16SourceDetectionTask.ConfigClass = SourceDetectionConfig 

17 

18 

19class IterateDetectionSkySubConfig(Config): 

20 niter = Field[int]( 

21 doc="Number of iterations", 

22 default=2, 

23 ) 

24 

25 detect = ConfigurableField[SourceDetectionConfig]( 

26 doc="Detection config", 

27 target=SourceDetectionTask 

28 ) 

29 

30 back = ConfigurableField[SubtractBackgroundConfig]( 

31 doc="Subtract background config", 

32 target=SubtractBackgroundTask 

33 ) 

34 

35 def setDefaults(self): 

36 super().setDefaults() 

37 

38 # detection 

39 self.detect.reEstimateBackground = False 

40 self.detect.thresholdValue = DEFAULT_THRESH 

41 

42 # background subtraction 

43 bp_to_skip = util.get_stats_mask() 

44 self.back = SubtractBackgroundConfig(ignoredPixelMask=bp_to_skip) 

45 

46 

47class IterateDetectionSkySubTask(Task): 

48 ConfigClass = IterateDetectionSkySubConfig 

49 _DefaultName = "iterate_detection_and_skysub" 

50 

51 def __init__(self, *args, **kwargs): 

52 super().__init__(*args, **kwargs) 

53 self.makeSubtask("detect") 

54 self.makeSubtask("back") 

55 

56 def run(self, exposure): 

57 if self.config.niter < 1: 

58 raise ValueError(f'niter {self.config.niter} is less than 1') 

59 

60 schema = afw_table.SourceTable.makeMinimalSchema() 

61 table = afw_table.SourceTable.make(schema) 

62 

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) 

69 

70 sky_meas += exposure.getMetadata()['BGMEAN'] 

71 

72 meta = exposure.getMetadata() 

73 

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 

84 

85 return result 

86 

87 

88class SubtractSkyMbExpConfig(Config): 

89 iterate_detection_and_skysub = ConfigurableField[IterateDetectionSkySubConfig]( 

90 doc="Iterate detection and sky subtraction config", 

91 target=IterateDetectionSkySubTask 

92 ) 

93 

94 def setDefaults(self): 

95 super().setDefaults() 

96 

97 self.iterate_detection_and_skysub.detect.thresholdValue = DEFAULT_THRESH 

98 self.iterate_detection_and_skysub.niter = 2 

99 

100 

101class SubtractSkyMbExpTask(Task): 

102 ConfigClass = SubtractSkyMbExpConfig 

103 _DefaultName = "subtract_sky_mbexp" 

104 

105 def __init__(self, *args, **kwargs): 

106 super().__init__(*args, **kwargs) 

107 self.makeSubtask("iterate_detection_and_skysub") 

108 

109 def run(self, mbexp): 

110 for exp in mbexp: 

111 self.iterate_detection_and_skysub.run(exposure=exp) 

112 

113 

114def determine_and_subtract_sky(exp): 

115 """ 

116 Determine and subtract the sky from the input exposure. 

117 The exposure is modified. 

118 

119 Parameters 

120 ---------- 

121 exp: Exposure 

122 The exposure to be processed 

123 """ 

124 

125 bp_to_skip = util.get_stats_mask(exp) 

126 back_config = SubtractBackgroundConfig(ignoredPixelMask=bp_to_skip) 

127 back_task = SubtractBackgroundTask(config=back_config) 

128 

129 # returns background data, but we are ignoring it for now 

130 background = back_task.run(exp) 

131 return background 

132 

133 

134def subtract_sky_mbexp(mbexp, thresh=DEFAULT_THRESH, config=None): 

135 """ 

136 subtract sky 

137 

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 {} 

146 

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'] = {} 

152 

153 detect_override = config_override['iterate_detection_and_skysub']['detect'] 

154 detect_override['thresholdValue'] = thresh 

155 

156 config = SubtractSkyMbExpConfig() 

157 config.setDefaults() 

158 

159 util.override_config(config, config_override) 

160 

161 config.freeze() 

162 config.validate() 

163 task = SubtractSkyMbExpTask(config=config) 

164 task.run(mbexp=mbexp) 

165 

166 

167def iterate_detection_and_skysub( 

168 exposure, thresh, niter=2, config=None 

169): 

170 """ 

171 Iterate detection and sky subtraction 

172 

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. 

182 

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') 

189 

190 config_override = config if config is not None else {} 

191 

192 # set threshold 

193 if 'detect' not in config_override: 

194 config_override['detect'] = {} 

195 config_override['detect']['thresholdValue'] = thresh 

196 

197 if niter: 

198 config_override['niter'] = niter 

199 

200 config = IterateDetectionSkySubConfig() 

201 config.setDefaults() 

202 

203 util.override_config(config, config_override) 

204 

205 task = IterateDetectionSkySubTask(config=config) 

206 result = task.run(exposure) 

207 return result