Coverage for python/lsst/meas/algorithms/variance_plane.py: 7%

38 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-24 10:22 +0000

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# 

22"""Utility functions related to the variance plane of Exposure objects. Tested 

23in `ip_isr/tests/test_variance_plane.py` to avoid circular dependencies. 

24""" 

25 

26import numpy as np 

27 

28__all__ = ["remove_signal_from_variance"] 

29 

30 

31def remove_signal_from_variance(exposure, gain=None, gains=None, average_across_amps=False, in_place=False): 

32 """ 

33 Removes the Poisson contribution from actual sources in the variance plane 

34 of an Exposure. 

35 

36 If neither gain nor gains are provided, the function estimates the gain(s). 

37 If ``average_across_amps`` is True, a single gain value for the entire 

38 image is estimated. If False, individual gain values for each amplifier are 

39 estimated. The estimation involves a linear fit of variance versus image 

40 plane. 

41 

42 Parameters 

43 ---------- 

44 exposure : `~lsst.afw.image.Exposure` 

45 The background-subtracted exposure containing a variance plane to be 

46 corrected for source contributions. 

47 gain : `float`, optional 

48 The gain value for the entire image. This parameter is used if 

49 ``gains`` is not provided. If both ``gain`` and ``gains`` are None, and 

50 ``average_across_amps`` is True, ``gain`` is estimated from the image 

51 and variance planes. 

52 gains : dict[`str`, `float`], optional 

53 A dictionary mapping amplifier ID (as a string) to gain value. This 

54 parameter is used if ``gain`` is not provided. If both ``gain`` and 

55 ``gains`` are None, and ``average_across_amps`` is False, ``gains`` are 

56 estimated from the image and variance planes. 

57 average_across_amps : `bool`, optional 

58 Determines the gain estimation strategy. If True, the gain for the 

59 entire image is estimated at once. If False, individual gains for each 

60 amplifier are estimated. This parameter is ignored if either ``gain`` 

61 or ``gains`` is specified. 

62 in_place : `bool`, optional 

63 If True, the variance plane of the input Exposure is modified in place. 

64 A modified copy of the variance plane is always returned irrespective 

65 of this. 

66 

67 Returns 

68 ------- 

69 variance_plane : `~lsst.afw.image.Image` 

70 The corrected variance plane, with the signal contribution removed. 

71 

72 Raises 

73 ------ 

74 AttributeError 

75 If amplifiers cannot be retrieved from the exposure. 

76 ValueError 

77 If both ``gain`` and ``gains`` are provided, or if the number of 

78 provided ``gains`` does not match the number of amplifiers. 

79 """ 

80 variance_plane = exposure.variance if in_place else exposure.variance.clone() 

81 if gain is None and gains is None: 

82 if average_across_amps: 

83 amp_bboxes = [exposure.getBBox()] 

84 else: 

85 try: 

86 amps = exposure.getDetector().getAmplifiers() 

87 amp_bboxes = [amp.getBBox() for amp in amps] 

88 except AttributeError: 

89 raise AttributeError( 

90 "Could not retrieve amplifiers from exposure. To compute a simple gain value across the " 

91 "entire image, use average_across_amps=True." 

92 ) 

93 # Fit a straight line to variance vs (sky-subtracted) signal. Then 

94 # evaluate that line at zero signal to get an estimate of the 

95 # signal-free variance. 

96 for amp_bbox in amp_bboxes: 

97 amp_im_arr = exposure[amp_bbox].image.array 

98 amp_var_arr = variance_plane[amp_bbox].array 

99 good = (amp_var_arr != 0) & np.isfinite(amp_var_arr) & np.isfinite(amp_im_arr) 

100 fit = np.polyfit(amp_im_arr[good], amp_var_arr[good], deg=1) 

101 # Fit is [1/gain, sky_var]. 

102 amp_gain = 1.0 / fit[0] 

103 variance_plane[amp_bbox].array[good] -= amp_im_arr[good] / amp_gain 

104 elif gain is None and gains is not None: 

105 amps = exposure.getDetector().getAmplifiers() 

106 namps = len(amps) 

107 if len(gains) != namps: 

108 raise ValueError( 

109 f"Incorrect number of gains provided: {len(gains)} values for {namps} amplifiers." 

110 ) 

111 for amp in amps: 

112 amp_bbox = amp.getBBox() 

113 amp_gain = gains[amp.getName()] 

114 im_arr = exposure[amp_bbox].image.array 

115 variance_plane[amp_bbox].array -= im_arr / amp_gain 

116 elif gain is not None and gains is None: 

117 im_arr = exposure.image.array 

118 variance_plane.array -= im_arr / gain 

119 elif gain is not None and gains is not None: 

120 raise ValueError( 

121 "Both 'gain' and 'gains' are provided. Please provide only one of them or none at " 

122 "all in case of automatic gain estimation from the image and variance planes." 

123 ) 

124 # Check that the variance plane has no negative values. 

125 if np.any(variance_plane.array < 0): 

126 raise ValueError("Corrected variance plane has negative values.") 

127 return variance_plane