Coverage for python/lsst/ip/isr/masking.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-07 23:20 +0000

1# This file is part of ip_isr. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21# import os 

22 

23__all__ = ["MaskingConfig", "MaskingTask", "DECamEdgeBleedMaskConfig", "DECamEdgeBleedMaskTask"] 

24 

25from lsst.pex.config import Config, Field 

26from lsst.pipe.base import Task 

27 

28from . import isrFunctions 

29 

30 

31class MaskingConfig(Config): 

32 doSpecificMasking = Field( 

33 dtype=bool, 

34 doc="Masking configuration.", 

35 default=False, 

36 ) 

37 

38 

39class MaskingTask(Task): 

40 """Perform extra masking for detector issues such as ghosts and glints. 

41 """ 

42 ConfigClass = MaskingConfig 

43 _DefaultName = "isrMasking" 

44 

45 def run(self, exposure): 

46 """Mask a known bad region of an exposure. 

47 

48 Parameters 

49 ---------- 

50 exposure : `lsst.afw.image.Exposure` 

51 Exposure to construct detector-specific masks for. 

52 

53 Returns 

54 ------- 

55 status : scalar 

56 This task is currently not implemented, and should be 

57 retargeted by a camera specific version. 

58 """ 

59 return 

60 

61 

62class DECamEdgeBleedMaskConfig(Config): 

63 satMinArea = Field( 

64 dtype=int, 

65 doc="Minimum area (pixels) of a saturated footprint to be considered.", 

66 default=10000, 

67 ) 

68 approachRows = Field( 

69 dtype=int, 

70 doc=("A saturated footprint must come within this many rows of the read edge to trigger the " 

71 "edge bleed check."), 

72 default=20, 

73 ) 

74 nSigma = Field( 

75 dtype=float, 

76 doc="A pixel is \"low\" if it is more than this many sigma below sky.", 

77 default=5.0, 

78 ) 

79 nRowsCheck = Field( 

80 dtype=int, 

81 doc="Number of rows from the read edge to check for an edge bleed dip.", 

82 default=20, 

83 ) 

84 nRowsCheckShort = Field( 

85 dtype=int, 

86 doc="A second, shorter window from the read edge to check for a short edge bleed dip.", 

87 default=5, 

88 ) 

89 minUsablePixelsPerRow = Field( 

90 dtype=int, 

91 doc=("Minimum number of pixels not saturated, bad or missing to search for dips. " 

92 "Rows with fewer pixels may be \"blocked\" due to a drained register."), 

93 default=30, 

94 ) 

95 minLowFraction = Field( 

96 dtype=float, 

97 doc=("Mean fraction of usable pixels that are low, over either nRowsCheck or nRowsCheckShort, " 

98 "to confirm a dip."), 

99 default=0.03, 

100 ) 

101 minLowFractionExtent = Field( 

102 dtype=float, 

103 doc=("Fraction of usable pixels that are low that each row must exceed to count toward the " 

104 "bleed height; the scan stops after five consecutive rows that do not. " 

105 "Should not exceed minLowFraction."), 

106 default=0.01, 

107 ) 

108 minBlockedRows = Field( 

109 dtype=int, 

110 doc="Minimum number of blocked rows beyond the normal border at the read edge to be masked if found.", 

111 default=5, 

112 ) 

113 marginFraction = Field( 

114 dtype=float, 

115 doc=("Extra rows masked beyond the measured height, as a fraction of that height " 

116 "(plus one row)."), 

117 default=0.125, 

118 ) 

119 saturatedMaskName = Field( 

120 dtype=str, 

121 doc="Name of mask plane holding saturated pixels; must match the parent ISR task.", 

122 default="SAT", 

123 ) 

124 

125 def validate(self): 

126 super().validate() 

127 if not 0.0 < self.minLowFraction < 1.0 or not 0.0 < self.minLowFractionExtent < 1.0: 

128 raise ValueError("minLowFraction and minLowFractionExtent must be between 0 and 1.") 

129 if self.minLowFractionExtent > self.minLowFraction: 

130 raise ValueError("minLowFractionExtent must not exceed minLowFraction.") 

131 

132 

133class DECamEdgeBleedMaskTask(MaskingTask): 

134 """Mask DECam-style edge bleeds: depressed rows next to the read register 

135 below a large saturated star. 

136 

137 Intended as a retarget for ``IsrTaskConfig.masking`` (enabled with 

138 ``doCameraSpecificMasking``). Must run after saturation masking. 

139 """ 

140 ConfigClass = DECamEdgeBleedMaskConfig 

141 _DefaultName = "decamEdgeBleedMask" 

142 

143 def run(self, exposure): 

144 """Mask edge bleeds in every amplifier of an exposure. 

145 

146 Parameters 

147 ---------- 

148 exposure : `lsst.afw.image.Exposure` 

149 Assembled exposure with saturation already masked. The mask 

150 plane is modified in place. 

151 """ 

152 isrFunctions.maskDECamEdgeBleed( 

153 exposure, 

154 satMinArea=self.config.satMinArea, 

155 approachRows=self.config.approachRows, 

156 nSigma=self.config.nSigma, 

157 nRowsCheck=self.config.nRowsCheck, 

158 nRowsCheckShort=self.config.nRowsCheckShort, 

159 minUsablePixelsPerRow=self.config.minUsablePixelsPerRow, 

160 minLowFraction=self.config.minLowFraction, 

161 minLowFractionExtent=self.config.minLowFractionExtent, 

162 minBlockedRows=self.config.minBlockedRows, 

163 marginFraction=self.config.marginFraction, 

164 saturatedMaskName=self.config.saturatedMaskName, 

165 log=self.log, 

166 )