Coverage for tests/test_scatterPlot.py: 22%

96 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-14 03:19 -0700

1# This file is part of analysis_drp. 

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 

23import os 

24import shutil 

25import tempfile 

26import unittest 

27 

28import lsst.utils.tests 

29import matplotlib 

30import matplotlib.pyplot as plt 

31import numpy as np 

32import pandas as pd 

33from lsst.analysis.tools.actions.plot.plotUtils import get_and_remove_figure_text 

34from lsst.analysis.tools.actions.plot.scatterplotWithTwoHists import ( 

35 ScatterPlotStatsAction, 

36 ScatterPlotWithTwoHists, 

37) 

38from lsst.analysis.tools.actions.vector.selectors import ( 

39 GalaxySelector, 

40 SnSelector, 

41 StarSelector, 

42 VectorSelector, 

43) 

44from lsst.analysis.tools.actions.vector.vectorActions import ( 

45 ConstantValue, 

46 DivideVector, 

47 DownselectVector, 

48 LoadVector, 

49 MagColumnNanoJansky, 

50 SubtractVector, 

51) 

52from lsst.analysis.tools.interfaces import AnalysisPlot 

53 

54matplotlib.use("Agg") 

55 

56ROOT = os.path.abspath(os.path.dirname(__file__)) 

57filename_texts_ref = os.path.join(ROOT, "data", "test_scatterPlot_texts.txt") 

58path_lines_ref = os.path.join(ROOT, "data", "test_scatterPlot_lines") 

59 

60 

61class ScatterPlotWithTwoHistsTaskTestCase(lsst.utils.tests.TestCase): 

62 """ScatterPlotWithTwoHistsTask test case.""" 

63 

64 def setUp(self): 

65 self.testDir = tempfile.mkdtemp(dir=ROOT, prefix="test_output") 

66 

67 # Set up a quasi-plausible measurement catalog 

68 mag = 12.5 + 2.5 * np.log10(np.arange(10, 100000)) 

69 flux = 10 ** (-0.4 * (mag - (mag[-1] + 1))) 

70 rng = np.random.default_rng(0) 

71 extendedness = 0.0 + (rng.uniform(size=len(mag)) < 0.99 * (mag - mag[0]) / (mag[-1] - mag[0])) 

72 flux_meas = flux + rng.normal(scale=np.sqrt(flux * (1 + extendedness))) 

73 flux_err = np.sqrt(flux_meas * (1 + extendedness)) 

74 good = (flux_meas / np.sqrt(flux * (1 + extendedness))) > 3 

75 extendedness = extendedness[good] 

76 flux = flux[good] 

77 flux_meas = flux_meas[good] 

78 flux_err = flux_err[good] 

79 

80 # Configure the plot to show observed vs true mags 

81 action = ScatterPlotWithTwoHists( 

82 xAxisLabel="mag", 

83 yAxisLabel="mag meas - ref", 

84 magLabel="mag", 

85 plotTypes=[ 

86 "stars", 

87 "galaxies", 

88 ], 

89 xLims=(20, 30), 

90 yLims=(-1000, 1000), 

91 ) 

92 plot = AnalysisPlot(produce=action) 

93 

94 # Load the relevant columns 

95 key_flux = "meas_Flux" 

96 plot.process.buildActions.fluxes_meas = LoadVector(vectorKey=key_flux) 

97 plot.process.buildActions.fluxes_err = LoadVector(vectorKey=f"{key_flux}Err") 

98 plot.process.buildActions.fluxes_ref = LoadVector(vectorKey="ref_Flux") 

99 plot.process.buildActions.mags_ref = MagColumnNanoJansky( 

100 vectorKey=plot.process.buildActions.fluxes_ref.vectorKey 

101 ) 

102 

103 # Compute the y-axis quantity 

104 plot.process.buildActions.diff = SubtractVector( 

105 actionA=MagColumnNanoJansky( 

106 vectorKey=plot.process.buildActions.fluxes_meas.vectorKey, returnMillimags=True 

107 ), 

108 actionB=DivideVector( 

109 actionA=plot.process.buildActions.mags_ref, 

110 actionB=ConstantValue(value=1e-3), 

111 ), 

112 ) 

113 

114 # Filter stars/galaxies, storing quantities separately 

115 plot.process.buildActions.galaxySelector = GalaxySelector(vectorKey="refExtendedness") 

116 plot.process.buildActions.starSelector = StarSelector(vectorKey="refExtendedness") 

117 for singular, plural in (("galaxy", "Galaxies"), ("star", "Stars")): 

118 setattr( 

119 plot.process.filterActions, 

120 f"x{plural}", 

121 DownselectVector( 

122 vectorKey="mags_ref", selector=VectorSelector(vectorKey=f"{singular}Selector") 

123 ), 

124 ) 

125 setattr( 

126 plot.process.filterActions, 

127 f"y{plural}", 

128 DownselectVector(vectorKey="diff", selector=VectorSelector(vectorKey=f"{singular}Selector")), 

129 ) 

130 setattr( 

131 plot.process.filterActions, 

132 f"flux{plural}", 

133 DownselectVector( 

134 vectorKey="fluxes_meas", selector=VectorSelector(vectorKey=f"{singular}Selector") 

135 ), 

136 ) 

137 setattr( 

138 plot.process.filterActions, 

139 f"flux{plural}Err", 

140 DownselectVector( 

141 vectorKey="fluxes_err", selector=VectorSelector(vectorKey=f"{singular}Selector") 

142 ), 

143 ) 

144 

145 # Compute low/high SN summary stats 

146 statAction = ScatterPlotStatsAction( 

147 vectorKey=f"y{plural}", 

148 fluxType=f"flux{plural}", 

149 highSNSelector=SnSelector(fluxType=f"flux{plural}", threshold=50), 

150 lowSNSelector=SnSelector(fluxType=f"flux{plural}", threshold=20), 

151 ) 

152 setattr(plot.process.calculateActions, plural.lower(), statAction) 

153 plot.produce = action 

154 

155 data = { 

156 "ref_Flux": flux, 

157 key_flux: flux_meas, 

158 f"{key_flux}Err": flux_err, 

159 "refExtendedness": extendedness, 

160 } 

161 

162 self.data = pd.DataFrame(data) 

163 self.plot = plot 

164 plotInfo = {key: "test" for key in ("plotName", "run", "tableName")} 

165 plotInfo["bands"] = [] 

166 self.plotInfo = plotInfo 

167 

168 def tearDown(self): 

169 if os.path.exists(self.testDir): 

170 shutil.rmtree(self.testDir, True) 

171 del self.data 

172 del self.plot 

173 del self.plotInfo 

174 del self.testDir 

175 

176 def test_ScatterPlotWithTwoHistsTask(self): 

177 plt.rcParams.update(plt.rcParamsDefault) 

178 result = self.plot( 

179 data=self.data, 

180 skymap=None, 

181 plotInfo=self.plotInfo, 

182 ) 

183 self.assertTrue(isinstance(result, plt.Figure)) 

184 

185 # Set to true to save plots as PNGs 

186 # Use matplotlib.testing.compare.compare_images if needed 

187 save_images = False 

188 if save_images: 

189 result.savefig(os.path.join(ROOT, "data", "test_scatterPlot.png")) 

190 

191 texts, lines = get_and_remove_figure_text(result) 

192 if save_images: 

193 result.savefig(os.path.join(ROOT, "data", "test_scatterPlot_unlabeled.png")) 

194 

195 # Set to true to re-generate reference data 

196 resave = False 

197 

198 # Compare line values 

199 for idx, line in enumerate(lines): 

200 filename = os.path.join(path_lines_ref, f"line_{idx}.txt") 

201 if resave: 

202 np.savetxt(filename, line) 

203 arr = np.loadtxt(filename) 

204 # Differences of order 1e-12 possible between MacOS and Linux 

205 # Plots are generally not expected to be that precise 

206 # Differences to 1e-3 should not be visible with this test data 

207 self.assertFloatsAlmostEqual(arr, line, atol=1e-3, rtol=1e-4) 

208 

209 # Ensure that newlines within labels are replaced by a sentinel 

210 newline = "\n" 

211 newline_replace = "[newline]" 

212 # Compare text labels 

213 if resave: 

214 with open(filename_texts_ref, "w") as f: 

215 f.writelines(f"{text.strip().replace(newline, newline_replace)}\n" for text in texts) 

216 

217 with open(filename_texts_ref, "r") as f: 

218 texts_ref = set(x.strip() for x in f.readlines()) 

219 texts_set = set(x.strip().replace(newline, newline_replace) for x in texts) 

220 

221 self.assertTrue(texts_set.issuperset(texts_ref)) 

222 

223 

224class MemoryTester(lsst.utils.tests.MemoryTestCase): 

225 pass 

226 

227 

228def setup_module(module): 

229 lsst.utils.tests.init() 

230 

231 

232if __name__ == "__main__": 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true

233 lsst.utils.tests.init() 

234 unittest.main()