Coverage for python / lsst / pipe / tasks / prettyPictureMaker / _functors / _local_contrast.py: 71%

47 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-25 08:39 +0000

1# This file is part of pipe_tasks. 

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 

22from __future__ import annotations 

23 

24__all__ = ("DiffusionFunction", "LocalContrastEnhancer") 

25 

26from lsst.pipe.tasks.prettyPictureMaker.types import FloatImagePlane 

27from lsst.pex.config.configurableActions import ConfigurableAction, ConfigurableActionField 

28from lsst.pex.config import Field 

29from lsst.rubinoxide import rgb 

30 

31from .._localContrast import localContrast 

32 

33 

34class DiffusionFunction(ConfigurableAction): 

35 """Apply anisotropic diffusion processing to enhance image details. 

36 

37 Anisotropic diffusion is a multi-scale image processing technique that 

38 selectively smooths regions while preserving edges by using spatially 

39 varying diffusion coefficients. This implementation uses wavelet-based 

40 anisotropic diffusion with configurable anisotropy parameters to control 

41 how different frequency components diffuse relative to their gradients. 

42 

43 The diffusion process works by: 

44 - Applying multiple iterations of gradient-based diffusion 

45 - Using different diffusion speeds for low and high frequency wavelets 

46 - Controlling diffusion direction via anisotropy parameters 

47 - Regularizing coefficients to detect and preserve edges 

48 - Modulating response to low-variance regions via variance threshold 

49 """ 

50 

51 iterations = Field[int]("number of interations in the diffusion process", default=3) 

52 anisotropy_first = Field[float]( 

53 "The diffusion direction of low-frequency wavelets relative to their own gradient orientation", 

54 default=1, 

55 ) 

56 anisotropy_second = Field[float]( 

57 "The diffusion direction of low-frequency wavelets relative to the high-frequency gradient.", 

58 default=1, 

59 ) 

60 anisotropy_third = Field[float]( 

61 "The diffusion direction of high-frequency wavelets relative to the low-frequency gradient.", 

62 default=1, 

63 ) 

64 anisotropy_fourth = Field[float]( 

65 "The diffusion direction of high-frequency wavelets relative to their own gradient orientation", 

66 default=1, 

67 ) 

68 regularization = Field[float]("Regularization of coefficients used to detect edges", default=2.94) 

69 variance_threshold = Field[float]( 

70 doc=( 

71 "The variance threshold modulates the filter's response to low-variance regions, with positive " 

72 " values enhancing local contrast and negative values suppressing noise and blur in those areas" 

73 ), 

74 default=0.0, 

75 ) 

76 radius_center = Field[float]( 

77 doc=( 

78 "The diffusion scale parameter: zero diffuses fine details (deblurring/denoising), " 

79 "while non-zero values selectively diffuse larger scales to enhance local contrast." 

80 ), 

81 default=0.0, 

82 ) 

83 radius = Field[float]( 

84 doc="The diffusion span defines a radial band (center ± span) for detail modification.", default=5.0 

85 ) 

86 first = Field[float](doc="Anisotropic diffusion speed of low-frequency wavelets", default=0.0065) 

87 second = Field[float]( 

88 doc="Low-frequency wavelet diffusion speed along the 2nd-order anisotropy axis", default=-0.25 

89 ) 

90 third = Field[float]( 

91 doc="High-frequency wavelet diffusion speed along the 3rd-order anisotropy axis", default=-0.25 

92 ) 

93 fourth = Field[float]( 

94 doc="High-frequency wavelet diffusion speed along the 4th-order anisotropy axis.", default=-0.2774 

95 ) 

96 sharpness = Field[float]( 

97 doc="Adjusts wavelet detail amplitude. Positive values sharpen, negative values blur.", default=0.0 

98 ) 

99 

100 def __call__(self, intensities: FloatImagePlane) -> FloatImagePlane: 

101 """Apply anisotropic diffusion to the input intensity image. 

102 

103 Parameters 

104 ---------- 

105 intensities : `FloatImagePlane` 

106 The input intensity image to process. 

107 

108 Returns 

109 ------- 

110 result : `FloatImagePlane` 

111 The diffused intensity image with enhanced details. 

112 

113 Notes 

114 ----- 

115 This method implements wavelet-based anisotropic diffusion: 

116 

117 1. Multi-scale decomposition: The image is analyzed across multiple 

118 frequency bands using wavelet decomposition. 

119 2. Directional diffusion: Low-frequency wavelets diffuse according to 

120 their own gradient orientation (anisotropy_first) and high-frequency 

121 gradients (anisotropy_second). High-frequency wavelets diffuse 

122 relative to low-frequency gradients (anisotropy_third) and their 

123 own gradients (anisotropy_fourth). 

124 3. Speed control: Diffusion speeds are configured via `first`, `second`, 

125 `third`, and `fourth` parameters for each anisotropy axis. 

126 4. Edge preservation: Regularization prevents diffusion across edges. 

127 Variance threshold modulates response to smooth regions. 

128 5. Scale selection: `radius_center` and `radius` define which scales 

129 are modified, enabling targeted enhancement or denoising. 

130 

131 The diffusion equation is solved iteratively for `iterations` steps, 

132 with the `sharpness` parameter adjusting final detail amplitudes. 

133 """ 

134 return rgb.diffuse_gray_image( 

135 intensities, 

136 iterations=self.iterations, 

137 radius_center=self.radius_center, 

138 radius=self.radius, 

139 regularization=self.regularization, 

140 anisotropy_first=self.anisotropy_first, 

141 anisotropy_second=self.anisotropy_second, 

142 anisotropy_third=self.anisotropy_third, 

143 anisotropy_fourth=self.anisotropy_fourth, 

144 first=self.first, 

145 second=self.second, 

146 third=self.third, 

147 fourth=self.fourth, 

148 variance_threshold=self.variance_threshold, 

149 sharpness=self.sharpness, 

150 ) 

151 

152 

153class LocalContrastEnhancer(ConfigurableAction): 

154 """Multi-stage local contrast enhancement processor. 

155 

156 Notes 

157 ----- 

158 This class implements a two-stage approach for enhancing image contrast: 

159 

160 1. **Local Contrast Enhancement**: Applies scale-space contrast enhancement 

161 using a Laplacian pyramid approach. This adjusts highlights, shadows, 

162 and clarity while operating on multiple resolution levels. 

163 

164 2. **Anisotropic Diffusion**: Optionally applies wavelet-based anisotropic 

165 diffusion to further sharpen details and preserve edges. This stage 

166 selectively smooths regions based on local gradient information. 

167 

168 The processing pipeline is configurable via parameters for both stages, 

169 allowing fine-tuned control over the enhancement behavior. 

170 """ 

171 

172 doLocalContrast = Field[bool]( 

173 "Do apply local contrast", 

174 default=True, 

175 deprecated=( 

176 "This will stop working in v31 and be removed in v32, please set doLocalContrast on" 

177 " PrettyPictureConfig" 

178 ), 

179 ) 

180 highlights = Field[float](doc="Adjustment factor for the highlights", default=-0.9) 

181 shadows = Field[float](doc="Adjustment factor for the shadows", default=0.5) 

182 clarity = Field[float](doc="Amount of clarity to apply to contrast modification", default=0.1) 

183 sigma = Field[float]( 

184 doc="The scale size of what is considered local in the contrast enhancement", default=30 

185 ) 

186 maxLevel = Field[int]( 

187 doc="The maximum number of scales the contrast should be enhanced over, if None then all", 

188 default=4, 

189 optional=True, 

190 ) 

191 skipLevels = Field[int]("Skip this many lowest levels in laplace pyramid", default=0) 

192 doDiffusion = Field[bool]("Run the diffusion function or not", default=True) 

193 diffusionFunction = ConfigurableActionField[DiffusionFunction]( 

194 doc="Diffusion function to enhance local contrast", 

195 ) 

196 

197 def setDefaults(self) -> None: 

198 self.diffusionFunction.iterations = 2 

199 self.diffusionFunction.radius_center = 280 

200 self.diffusionFunction.radius = 400 

201 self.diffusionFunction.regularization = 0.0 

202 self.diffusionFunction.first = -1.25 

203 self.diffusionFunction.third = 0.0 

204 self.diffusionFunction.fourth = 0.0 

205 

206 def __call__(self, intensities: FloatImagePlane) -> FloatImagePlane: 

207 """Apply multi-stage contrast enhancement to the input image. 

208 

209 Parameters 

210 ---------- 

211 intensities : `FloatImagePlane` 

212 The input intensity image to process. 

213 

214 Returns 

215 ------- 

216 result : `FloatImagePlane` 

217 The enhanced intensity image with improved local contrast. 

218 

219 Notes 

220 ----- 

221 This method implements a two-stage enhancement pipeline: 

222 

223 1. **Local Contrast Enhancement** (via `localContrast`): 

224 - Builds a Laplacian pyramid of the input image 

225 - Applies scale-dependent contrast modifications 

226 - Adjusts highlights and shadows via `highlights` and `shadows` 

227 - Controls clarity and sharpness via `clarity` parameter 

228 - Operates over `maxLevel` scales, skipping `skipLevels` lowest 

229 - Uses `sigma` to define what is considered "local" 

230 

231 2. **Anisotropic Diffusion** (optional, via `diffusionFunction`): 

232 - Applied only if `doDiffusion=True` 

233 - Performs wavelet-based anisotropic diffusion 

234 - Preserves edges while enhancing details 

235 - Configurable via diffusionFunction parameters 

236 

237 The two stages are applied sequentially, with the diffusion stage 

238 operating on the locally enhanced image to further refine details. 

239 """ 

240 intensities = localContrast( 

241 intensities, 

242 sigma=self.sigma, 

243 highlights=self.highlights, 

244 shadows=self.shadows, 

245 clarity=self.clarity, 

246 maxLevel=self.maxLevel, 

247 skipLevels=self.skipLevels, 

248 ) 

249 if self.doDiffusion: 

250 intensities = self.diffusionFunction(intensities) 

251 return intensities