Coverage for python / lsst / ip / isr / binImageDataTask.py: 33%

49 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-17 09:10 +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 

22__all__ = ["BinImageDataTask", "BinImageDataConfig", "binImageData"] 

23 

24import lsst.afw.image as afwImage 

25import lsst.afw.math as afwMath 

26import lsst.pex.config as pexConfig 

27import lsst.pipe.base as pipeBase 

28import lsst.pipe.base.connectionTypes as cT 

29from lsst.utils.timer import timeMethod 

30 

31 

32class BinImageDataConnections( 

33 pipeBase.PipelineTaskConnections, 

34 dimensions=("instrument", "exposure", "detector"), 

35 defaultTemplates={"inputName": "postISRCCD", "outputName": "postISRCCDBin"}, 

36): 

37 

38 inputData = cT.Input( 

39 name="{inputName}", 

40 doc="Input image data to bin.", 

41 storageClass="ExposureF", 

42 dimensions=["instrument", "exposure", "detector"], 

43 ) 

44 outputData = cT.Output( 

45 name="{outputName}", 

46 doc="Binned image data.", 

47 storageClass="ExposureF", 

48 dimensions=["instrument", "exposure", "detector"], 

49 ) 

50 

51 def __init__(self, *, config=None): 

52 """Customize the connections and storageClass for a specific 

53 instance. This enables both to be dynamically set at runtime, 

54 allowing BinImageDataTask to work with different types of 

55 image and image-like data. 

56 

57 Parameters 

58 ---------- 

59 config : `BinExposureConfig` 

60 A config for `BinExposureTask`. 

61 """ 

62 super().__init__(config=config) 

63 if config and config.inputDimensions != self.inputData.dimensions: 

64 self.dimensions.clear() 

65 self.dimensions.update(config.inputDimensions) 

66 self.inputData = cT.Input( 

67 name=self.inputData.name, 

68 doc=self.inputData.doc, 

69 storageClass=self.inputData.storageClass, 

70 dimensions=frozenset(config.inputDimensions), 

71 ) 

72 self.outputData = cT.Output( 

73 name=self.outputData.name, 

74 doc=self.outputData.doc, 

75 storageClass=self.outputData.storageClass, 

76 dimensions=frozenset(config.inputDimensions), 

77 ) 

78 if config and config.inputStorageClass != self.inputData.storageClass: 

79 self.inputData = cT.Input( 

80 name=self.inputData.name, 

81 doc=self.inputData.doc, 

82 storageClass=config.inputStorageClass, 

83 dimensions=self.inputData.dimensions, 

84 ) 

85 self.outputData = cT.Output( 

86 name=self.outputData.name, 

87 doc=self.outputData.doc, 

88 storageClass=config.inputStorageClass, 

89 dimensions=self.outputData.dimensions, 

90 ) 

91 

92 

93class BinImageDataConfig( 

94 pipeBase.PipelineTaskConfig, pipelineConnections=BinImageDataConnections 

95): 

96 """Config for BinImageDataTask""" 

97 

98 inputDimensions = pexConfig.ListField( 

99 # Sort to ensure default order is consistent between runs 

100 default=sorted(BinImageDataConnections.dimensions), 

101 dtype=str, 

102 doc="Override for the dimensions of the input and output data.", 

103 ) 

104 inputStorageClass = pexConfig.Field( 

105 default="ExposureF", 

106 dtype=str, 

107 doc=( 

108 "Override the storageClass of the input and output data. " 

109 "Must be of type `Image`, `MaskedImage`, or `Exposure`, " 

110 "or one of their subtypes." 

111 ), 

112 ) 

113 binFactor = pexConfig.Field( 

114 dtype=int, 

115 doc="Binning factor applied to both spatial dimensions.", 

116 default=8, 

117 check=lambda x: x > 1, 

118 ) 

119 

120 

121class BinImageDataTask(pipeBase.PipelineTask): 

122 """Perform an nxn binning of an image or image-like dataset. 

123 

124 The binning factor is the same in both spatial dimensions (i.e., 

125 an nxn binning is performed). In the case of MaskedImages and Exposures, 

126 each of the input image planes are binned by the same factor. 

127 """ 

128 

129 ConfigClass = BinImageDataConfig 

130 _DefaultName = "binImageData" 

131 

132 @timeMethod 

133 def run(self, inputData, binFactor=None): 

134 """Perform an nxn binning of image and image-like data. 

135 

136 Parameters: 

137 ----------- 

138 inputData : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` or 

139 `lsst.afw.image.Exposure` or one of their sub-types. 

140 Data to spatially bin 

141 binFactor : `int`, optional. 

142 nxn binning factor. If not provided then self.config.binFactor 

143 is used. 

144 

145 Returns: 

146 -------- 

147 result : `lsst.pipe.base.Struct` 

148 Results as a struct with attributes: 

149 

150 ``outputData`` 

151 Binned data (`lsst.afw.image.Image` or 

152 `lsst.afw.image.MaskedImage` or `lsst.afw.image.Exposure` 

153 or one of their sub-types. The type matches that of the input.). 

154 """ 

155 if not binFactor: 

156 binFactor = self.config.binFactor 

157 return pipeBase.Struct(outputData=binImageData(inputData, binFactor)) 

158 

159 

160def binImageData(inputData, binFactor=8): 

161 """Bin image and image-like data to reduce its spatial dimensions. 

162 

163 Performs an nxn binning of the input data, reducing both spatial 

164 dimensions of each of the input image data by the provided 

165 factor. 

166 

167 Parameters: 

168 ----------- 

169 inputData: `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` or 

170 `lsst.afw.image.Exposure` or one of their sub-types. 

171 Input data to bin. 

172 binFactor: `int` 

173 Binning factor to apply to each input exposure's image data. 

174 Default 8. 

175 

176 Returns: 

177 -------- 

178 binnedImage or binnedExposure: `lsst.afw.image.Image` or 

179 `lsst.afw.image.MaskedImage` or `lsst.afw.image.Exposure` or one of 

180 their sub-types. 

181 Binned version of input image. 

182 

183 Raises 

184 ------ 

185 TypeError 

186 Raised if either the binning factor is not of type `int`, or if the 

187 input data to be binned is not of type `lsst.afw.image.Exposure` 

188 or one of its sub-types. 

189 """ 

190 

191 if not isinstance(binFactor, int): 

192 raise TypeError("binFactor must be of type int") 

193 

194 if isinstance(inputData, afwImage.Exposure): 

195 inputImage = inputData.getMaskedImage() 

196 isExposure = True 

197 elif isinstance(inputData, (afwImage.Image, afwImage.MaskedImage)): 

198 inputImage = inputData 

199 isExposure = False 

200 else: 

201 message = ( 

202 "inputData must be of type `lsst.afw.image.Image`, `lsst.afw.MaskedImage`, " 

203 "or `lsst.afw.image.Exposure`, or one of their sub-types." 

204 ) 

205 raise TypeError(message) 

206 

207 binnedImage = afwMath.binImage(inputImage, binFactor) 

208 

209 if isExposure: 

210 binnedExposure = afwImage.makeExposure(binnedImage) 

211 binnedExposure.setInfo(inputData.getInfo()) 

212 return binnedExposure 

213 else: 

214 return binnedImage