Coverage for tests/test_finalizeCharacterization.py: 18%

104 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-01 04:34 -0700

1# This file is part of pipe_tasks. 

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"""Test FinalizeCharacterizationTask. 

23""" 

24import logging 

25import unittest 

26import numpy as np 

27import pandas as pd 

28 

29import lsst.utils.tests 

30import lsst.afw.table as afwTable 

31import lsst.pipe.base as pipeBase 

32 

33from lsst.pipe.tasks.finalizeCharacterization import (FinalizeCharacterizationConfig, 

34 FinalizeCharacterizationTask) 

35 

36 

37class TestFinalizeCharacterizationTask(FinalizeCharacterizationTask): 

38 """A derived class which skips the initialization routines. 

39 """ 

40 __test__ = False # Stop Pytest from trying to parse as a TestCase 

41 

42 def __init__(self, **kwargs): 

43 pipeBase.PipelineTask.__init__(self, **kwargs) 

44 

45 self.makeSubtask('reserve_selection') 

46 self.makeSubtask('source_selector') 

47 

48 

49class FinalizeCharacterizationTestCase(lsst.utils.tests.TestCase): 

50 """Tests of some functionality of FinalizeCharacterizationTask. 

51 

52 Full testing comes from integration tests such as ci_hsc and ci_imsim. 

53 

54 These tests bypass the middleware used for accessing data and 

55 managing Task execution. 

56 """ 

57 def setUp(self): 

58 config = FinalizeCharacterizationConfig() 

59 

60 self.finalizeCharacterizationTask = TestFinalizeCharacterizationTask( 

61 config=config, 

62 ) 

63 

64 self.isolated_star_cat_dict, self.isolated_star_source_dict = self._make_isocats() 

65 

66 def _make_isocats(self): 

67 """Make test isolated star catalogs. 

68 

69 Returns 

70 ------- 

71 isolated_star_cat_dict : `dict` 

72 Per-"tract" dict of isolated star catalogs. 

73 isolate_star_source_dict : `dict` 

74 Per-"tract" dict of isolated source catalogs. 

75 """ 

76 dtype_cat = [('isolated_star_id', 'i8'), 

77 ('ra', 'f8'), 

78 ('dec', 'f8'), 

79 ('primary_band', 'U2'), 

80 ('source_cat_index', 'i4'), 

81 ('nsource', 'i4'), 

82 ('source_cat_index_i', 'i4'), 

83 ('nsource_i', 'i4'), 

84 ('source_cat_index_r', 'i4'), 

85 ('nsource_r', 'i4'), 

86 ('source_cat_index_z', 'i4'), 

87 ('nsource_z', 'i4')] 

88 

89 dtype_source = [('sourceId', 'i8'), 

90 ('obj_index', 'i4')] 

91 

92 isolated_star_cat_dict = {} 

93 isolated_star_source_dict = {} 

94 

95 np.random.seed(12345) 

96 

97 # There are 90 stars in both r, i. 10 individually in each. 

98 nstar = 110 

99 nsource_per_band_per_star = 2 

100 self.nstar_total = nstar 

101 self.nstar_per_band = nstar - 10 

102 

103 # This is a brute-force assembly of a star catalog and matched sources. 

104 for tract in [0, 1, 2]: 

105 ra = np.random.uniform(low=tract, high=tract + 1.0, size=nstar) 

106 dec = np.random.uniform(low=0.0, high=1.0, size=nstar) 

107 

108 cat = np.zeros(nstar, dtype=dtype_cat) 

109 cat['isolated_star_id'] = tract*nstar + np.arange(nstar) 

110 cat['ra'] = ra 

111 cat['dec'] = dec 

112 if tract < 2: 

113 cat['primary_band'][0: 100] = 'i' 

114 cat['primary_band'][100:] = 'r' 

115 else: 

116 # Tract 2 only has z band. 

117 cat['primary_band'][:] = 'z' 

118 

119 source_cats = [] 

120 counter = 0 

121 for i in range(cat.size): 

122 cat['source_cat_index'][i] = counter 

123 if tract < 2: 

124 if i < 90: 

125 cat['nsource'][i] = 2*nsource_per_band_per_star 

126 bands = ['r', 'i'] 

127 else: 

128 cat['nsource'][i] = nsource_per_band_per_star 

129 if i < 100: 

130 bands = ['i'] 

131 else: 

132 bands = ['r'] 

133 else: 

134 cat['nsource'][i] = nsource_per_band_per_star 

135 bands = ['z'] 

136 

137 for band in bands: 

138 cat[f'source_cat_index_{band}'][i] = counter 

139 cat[f'nsource_{band}'][i] = nsource_per_band_per_star 

140 source_cat = np.zeros(nsource_per_band_per_star, dtype=dtype_source) 

141 source_cat['sourceId'] = np.arange( 

142 tract*nstar + counter, 

143 tract*nstar + counter + nsource_per_band_per_star 

144 ) 

145 source_cat['obj_index'] = i 

146 

147 source_cats.append(source_cat) 

148 

149 counter += nsource_per_band_per_star 

150 

151 source_cat = np.concatenate(source_cats) 

152 

153 isolated_star_cat_dict[tract] = pipeBase.InMemoryDatasetHandle(pd.DataFrame(cat), 

154 storageClass="DataFrame") 

155 isolated_star_source_dict[tract] = pipeBase.InMemoryDatasetHandle(pd.DataFrame(source_cat), 

156 storageClass="DataFrame") 

157 

158 return isolated_star_cat_dict, isolated_star_source_dict 

159 

160 def test_concat_isolated_star_cats(self): 

161 """Test concatenation and reservation of the isolated star catalogs. 

162 """ 

163 

164 for band in ['r', 'i']: 

165 iso, iso_src = self.finalizeCharacterizationTask.concat_isolated_star_cats( 

166 band, 

167 self.isolated_star_cat_dict, 

168 self.isolated_star_source_dict 

169 ) 

170 

171 # There are two tracts, so double everything. 

172 self.assertEqual(len(iso), 2*self.nstar_per_band) 

173 

174 reserve_fraction = self.finalizeCharacterizationTask.config.reserve_selection.reserve_fraction 

175 self.assertEqual(np.sum(iso['reserved']), 

176 int(reserve_fraction*len(iso))) 

177 

178 # 2 tracts, 4 observations per tract per star, minus 2*10 not in the given band. 

179 self.assertEqual(len(iso_src), 2*(4*len(iso)//2 - 20)) 

180 

181 # Check that every star is properly matched to the sources. 

182 for i in range(len(iso)): 

183 np.testing.assert_array_equal( 

184 iso_src['obj_index'][iso[f'source_cat_index_{band}'][i]: 

185 iso[f'source_cat_index_{band}'][i] + iso[f'nsource_{band}'][i]], 

186 i 

187 ) 

188 

189 # Check that every reserved star is marked as a reserved source. 

190 res_star, = np.where(iso['reserved']) 

191 for i in res_star: 

192 np.testing.assert_array_equal( 

193 iso_src['reserved'][iso[f'source_cat_index_{band}'][i]: 

194 iso[f'source_cat_index_{band}'][i] + iso[f'nsource_{band}'][i]], 

195 True 

196 ) 

197 

198 # Check that every reserved source is marked as a reserved star. 

199 res_src, = np.where(iso_src['reserved']) 

200 np.testing.assert_array_equal( 

201 iso['reserved'][iso_src['obj_index'][res_src]], 

202 True 

203 ) 

204 

205 def test_concat_isolate_star_cats_no_sources(self): 

206 """Test concatenation when there are no sources in a tract.""" 

207 iso, iso_src = self.finalizeCharacterizationTask.concat_isolated_star_cats( 

208 'z', 

209 self.isolated_star_cat_dict, 

210 self.isolated_star_source_dict 

211 ) 

212 

213 self.assertGreater(len(iso), 0) 

214 

215 def test_compute_psf_and_ap_corr_map_no_sources(self): 

216 """Test log message when there are no good sources after selection.""" 

217 # Create an empty source catalog. 

218 src_schema = afwTable.SourceTable.makeMinimalSchema() 

219 src_schema.addField('base_GaussianFlux_instFlux', type='F', doc='Flux field') 

220 src_schema.addField('base_GaussianFlux_instFluxErr', type='F', doc='Flux field') 

221 src = afwTable.SourceCatalog(src_schema) 

222 

223 # Set defaults and placeholders for required positional arguments. 

224 self.finalizeCharacterizationTask.config.source_selector['science'].flags.bad = [] 

225 visit = 0 

226 detector = 0 

227 exposure = None 

228 isolated_source_table = None 

229 with self.assertLogs(level=logging.WARNING) as cm: 

230 psf, ap_corr_map, measured_src = self.finalizeCharacterizationTask.compute_psf_and_ap_corr_map( 

231 visit, 

232 detector, 

233 exposure, 

234 src, 

235 isolated_source_table 

236 ) 

237 self.assertIn( 

238 "No good sources remain after cuts for visit {}, detector {}".format(visit, detector), 

239 cm.output[0] 

240 ) 

241 

242 

243class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

244 pass 

245 

246 

247def setup_module(module): 

248 lsst.utils.tests.init() 

249 

250 

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

252 lsst.utils.tests.init() 

253 unittest.main()