LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
LSST Data Management Base Package
Loading...
Searching...
No Matches
accumulator_mean_stack.py
Go to the documentation of this file.
1# This file is part of meas_algorithms.
2#
3# LSST Data Management System
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6# See COPYRIGHT file at the top of the source tree.
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22import warnings
23
24import numpy as np
25
26
27__all__ = ['AccumulatorMeanStack']
28
29
31 """Stack masked images.
32
33 Parameters
34 ----------
35 shape : `tuple`
36 Shape of the input and output images.
37 bit_mask_value : `int`
38 Bit mask to flag for "bad" inputs that should not be stacked.
39 mask_threshold_dict : `dict` [`int`: `float`], optional
40 Dictionary of mapping from bit number to threshold for flagging.
41 Only bad bits (in bit_mask_value) which mask fractional weight
42 greater than this threshold will be flagged in the output image.
43 mask_map : `list` [`tuple`], optional
44 Mapping from input image bits to aggregated coadd bits.
45 no_good_pixels_mask : `int`, optional
46 Bit mask to set when there are no good pixels in the stack.
47 If not set then will set coadd masked image 'NO_DATA' bit.
48 calc_error_from_input_variance : `bool`, optional
49 Calculate the error from the input variance?
50 compute_n_image : `bool`, optional
51 Calculate the n_image map as well as stack?
52 """
53 def __init__(self, shape,
54 bit_mask_value, mask_threshold_dict={},
55 mask_map=[], no_good_pixels_mask=None,
56 calc_error_from_input_variance=True,
57 compute_n_image=False):
58 self.shape = shape
59 self.bit_mask_value = bit_mask_value
60 self.mask_map = mask_map
61 self.no_good_pixels_mask = no_good_pixels_mask
62 self.calc_error_from_input_variance = calc_error_from_input_variance
63 self.compute_n_image = compute_n_image
64
65 # Only track threshold bits that are in the bad bit_mask_value.
67 for bit in mask_threshold_dict:
68 if (self.bit_mask_value & 2**bit) > 0:
69 self.mask_threshold_dict[bit] = mask_threshold_dict[bit]
70
71 # sum_weight holds the sum of weights for each pixel.
72 self.sum_weight = np.zeros(shape, dtype=np.float64)
73 # sum_wdata holds the sum of weight*data for each pixel.
74 self.sum_wdata = np.zeros(shape, dtype=np.float64)
75
76 if calc_error_from_input_variance:
77 # sum_w2var holds the sum of weight**2 * variance for each pixel.
78 self.sum_w2var = np.zeros(shape, dtype=np.float64)
79 else:
80 # sum_weight2 holds the sum of weight**2 for each pixel.
81 self.sum_weight2 = np.zeros(shape, dtype=np.float64)
82 # sum_wdata2 holds the sum of weight * data**2 for each pixel.
83 self.sum_wdata2 = np.zeros(shape, dtype=np.float64)
84
85 self.or_mask = np.zeros(shape, dtype=np.int64)
87 for bit in self.mask_threshold_dict:
88 self.rejected_weights_by_bit[bit] = np.zeros(shape, dtype=np.float64)
89
90 self.masked_pixels_mask = np.zeros(shape, dtype=np.int64)
91
92 if self.compute_n_image:
93 self.n_image = np.zeros(shape, dtype=np.int32)
94
95 def add_masked_image(self, masked_image, weight=1.0):
96 """Add a masked image to the stack.
97
98 Parameters
99 ----------
100 masked_image : `lsst.afw.image.MaskedImage`
101 Masked image to add to the stack.
102 weight : `float`, optional
103 Weight to apply for weighted mean.
104 """
105 good_pixels = np.where(((masked_image.mask.array & self.bit_mask_value) == 0)
106 & np.isfinite(masked_image.mask.array))
107
108 self.sum_weight[good_pixels] += weight
109 self.sum_wdata[good_pixels] += weight*masked_image.image.array[good_pixels]
110
111 if self.compute_n_image:
112 self.n_image[good_pixels] += 1
113
115 self.sum_w2var[good_pixels] += (weight**2.)*masked_image.variance.array[good_pixels]
116 else:
117 self.sum_weight2[good_pixels] += weight**2.
118 self.sum_wdata2[good_pixels] += weight*(masked_image.image.array[good_pixels]**2.)
119
120 # Mask bits are propagated for good pixels
121 self.or_mask[good_pixels] |= masked_image.mask.array[good_pixels]
122
123 # Bad pixels are only tracked if they cross a threshold
124 for bit in self.mask_threshold_dict:
125 bad_pixels = ((masked_image.mask.array & 2**bit) > 0)
126 self.rejected_weights_by_bit[bit][bad_pixels] += weight
127 self.masked_pixels_mask[bad_pixels] |= 2**bit
128
129 def fill_stacked_masked_image(self, stacked_masked_image):
130 """Fill the stacked mask image after accumulation.
131
132 Parameters
133 ----------
134 stacked_masked_image : `lsst.afw.image.MaskedImage`
135 Total masked image.
136 """
137 with warnings.catch_warnings():
138 # Let the NaNs through and flag bad pixels below
139 warnings.simplefilter("ignore")
140
141 # The image plane is sum(weight*data)/sum(weight)
142 stacked_masked_image.image.array[:, :] = self.sum_wdata/self.sum_weight
143
145 mean_var = self.sum_w2var/(self.sum_weight**2.)
146 else:
147 # Compute the biased estimator
148 variance = self.sum_wdata2/self.sum_weight - stacked_masked_image.image.array[:, :]**2.
149 # De-bias
150 variance *= (self.sum_weight**2.)/(self.sum_weight**2. - self.sum_weight2)
151
152 # Compute the mean variance
153 mean_var = variance*self.sum_weight2/(self.sum_weight**2.)
154
155 stacked_masked_image.variance.array[:, :] = mean_var
156
157 # Propagate bits when they cross the threshold
158 for bit in self.mask_threshold_dict:
159 hypothetical_total_weight = self.sum_weight + self.rejected_weights_by_bit[bit]
160 self.rejected_weights_by_bit[bit] /= hypothetical_total_weight
161 propagate = np.where(self.rejected_weights_by_bit[bit] > self.mask_threshold_dict[bit])
162 self.or_mask[propagate] |= 2**bit
163
164 # Map mask planes to new bits for pixels that had at least one
165 # bad input rejected and are in the mask_map.
166 for mask_tuple in self.mask_map:
167 self.or_mask[(self.masked_pixels_mask & mask_tuple[0]) > 0] |= mask_tuple[1]
168
169 stacked_masked_image.mask.array[:, :] = self.or_mask
170
171 if self.no_good_pixels_mask is None:
172 mask_dict = stacked_masked_image.mask.getMaskPlaneDict()
173 no_good_pixels_mask = 2**(mask_dict['NO_DATA'])
174 else:
175 no_good_pixels_mask = self.no_good_pixels_mask
176
177 bad_pixels = (self.sum_weight <= 0.0)
178 stacked_masked_image.mask.array[bad_pixels] |= no_good_pixels_mask
179
180 def add_image(self, image, weight=1.0):
181 """Add an image to the stack.
182
183 No bit-filtering is performed when adding an image.
184
185 Parameters
186 ----------
187 image : `lsst.afw.image.Image`
188 Image to add to the stack.
189 weight : `float`, optional
190 Weight to apply for weighted mean.
191 """
192 self.sum_weight[:, :] += weight
193 self.sum_wdata[:, :] += weight*image.array[:]
194
195 if self.compute_n_image:
196 self.n_image[:, :] += 1
197
198 def fill_stacked_image(self, stacked_image):
199 """Fill the image after accumulation.
200
201 Parameters
202 ----------
203 stacked_image : `lsst.afw.image.Image`
204 Total image.
205 """
206 with warnings.catch_warnings():
207 # Let the NaNs through, this should only happen
208 # if we're stacking with no inputs.
209 warnings.simplefilter("ignore")
210
211 # The image plane is sum(weight*data)/sum(weight)
212 stacked_image.array[:, :] = self.sum_wdata/self.sum_weight
213
214 @staticmethod
216 """Convert stats control to threshold dict.
217
218 Parameters
219 ----------
220 stats_ctrl : `lsst.afw.math.StatisticsControl`
221
222 Returns
223 -------
224 threshold_dict : `dict`
225 Dict mapping from bit to propagation threshold.
226 """
227 threshold_dict = {}
228 for bit in range(64):
229 threshold_dict[bit] = stats_ctrl.getMaskPropagationThreshold(bit)
230
231 return threshold_dict
__init__(self, shape, bit_mask_value, mask_threshold_dict={}, mask_map=[], no_good_pixels_mask=None, calc_error_from_input_variance=True, compute_n_image=False)