Coverage for src / lstautorta / Ring_Background_Maps_ACADA.py: 0%

144 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 11:56 +0000

1#!/usr/bin/env python 

2 

3import astropy 

4import gammapy 

5import matplotlib 

6import numpy as np 

7import regions 

8 

9print("gammapy:", gammapy.__version__) 

10print("numpy:", np.__version__) 

11print("astropy", astropy.__version__) 

12print("regions", regions.__version__) 

13print("matplotlib", matplotlib.__version__) 

14 

15import os 

16 

17import astropy.units as u 

18import matplotlib.pyplot as plt 

19import numpy as np 

20from astropy.coordinates import SkyCoord 

21from matplotlib import style 

22 

23style.use("tableau-colorblind10") 

24import argparse 

25from pathlib import Path 

26 

27from acceptance_modelisation import RadialAcceptanceMapCreator 

28from gammapy.data import DataStore 

29from gammapy.datasets import ( 

30 Datasets, 

31 MapDataset, 

32) 

33from gammapy.estimators import ExcessMapEstimator 

34from gammapy.makers import ( 

35 MapDatasetMaker, 

36 RingBackgroundMaker, 

37 SafeMaskMaker, 

38) 

39from gammapy.maps import MapAxis, WcsGeom 

40from matplotlib.offsetbox import AnchoredText 

41from regions import CircleSkyRegion 

42from scipy.stats import norm 

43 

44parser = argparse.ArgumentParser( 

45 description="Automatic Script for the DL1 check", formatter_class=argparse.ArgumentDefaultsHelpFormatter 

46) 

47parser.add_argument("-d", "--directory", default="/fefs/onsite/pipeline/rta/data/", help="Directory for data") 

48parser.add_argument("-da", "--date", default="20230705", help="Date of the run to check") 

49parser.add_argument("-r", "--run-id", default="13600", help="run id to check") 

50parser.add_argument("-add", "--add-string", default="reco/", help="add a string to the path") 

51parser.add_argument("-RA", "--right-ascension", default="270.19042", help="right-ascension in deg") 

52parser.add_argument("-DEC", "--declination", default="78.46806", help="declination in deg") 

53 

54args = parser.parse_args() 

55config = vars(args) 

56 

57location_data = ( 

58 config["directory"] + config["date"] + "/" + config["run_id"] + "/" + config["add_string"] + "/plots" 

59) # path to DL3 folder 

60source_name = config["run_id"] # e.g., Crab, GRB210807A 

61cut_type = "standard" # e.g., loose, hard, ... 

62filename_output = f"{source_name}_{cut_type}" 

63 

64source_position = SkyCoord(ra=config["right_ascension"], dec=config["declination"], unit="deg", frame="icrs") 

65max_offset_run = 5 * u.deg 

66work_directory = location_data 

67path_plot = Path(work_directory + "/") 

68print(work_directory + "/") 

69path_plot.mkdir(exist_ok=True) 

70path_background = Path(work_directory + "/") 

71path_background.mkdir(exist_ok=True) 

72 

73e_min = 0.05 * u.TeV 

74e_max = 10.0 * u.TeV 

75n_bin_per_decade = 10 

76on_radius = 0.2 * u.deg 

77exclusion_radius = 0.35 * u.deg 

78fov_observation = 4.5 * u.deg 

79 

80r_in = 0.5 * u.deg 

81width = 0.4 * u.deg 

82correlation_radius = 0.2 * u.deg 

83 

84n_bin_per_decade_acceptance = 2.5 

85offset_bin_size_acceptance = 0.4 * u.deg 

86 

87data_store = DataStore.from_dir(location_data) 

88data_store.info() 

89 

90obs_ids = data_store.obs_table[source_position.separation(data_store.obs_table.pointing_radec) < max_offset_run][ 

91 "OBS_ID" 

92] 

93obs_collection = data_store.get_observations(obs_ids, required_irf=None) 

94 

95exclude_region = CircleSkyRegion(center=source_position, radius=exclusion_radius) 

96 

97n_bin_energy = int((np.log10(e_max.to_value(u.TeV)) - np.log10(e_min.to_value(u.TeV))) * n_bin_per_decade) 

98energy_axis = MapAxis.from_edges( 

99 np.logspace(np.log10(e_min.to_value(u.TeV)), np.log10(e_max.to_value(u.TeV)), n_bin_energy + 1), 

100 unit="TeV", 

101 name="energy", 

102 interp="log", 

103) 

104maximal_run_separation = np.max( 

105 source_position.separation(data_store.obs_table[np.isin(data_store.obs_table["OBS_ID"], obs_ids)].pointing_radec) 

106) 

107geom = WcsGeom.create( 

108 skydir=source_position, 

109 width=((maximal_run_separation + fov_observation) * 1.5, (maximal_run_separation + fov_observation) * 1.5), 

110 binsz=0.02, 

111 frame="icrs", 

112 axes=[energy_axis], 

113) 

114 

115geom_image = geom.to_image() 

116exclusion_mask = ~geom_image.region_mask([exclude_region]) 

117 

118stacked = MapDataset.create(geom=geom, name=source_name + "_stacked") 

119unstacked = Datasets() 

120maker = MapDatasetMaker(selection=["counts"]) 

121maker_safe_mask = SafeMaskMaker(methods=["offset-max"], offset_max=fov_observation) 

122 

123for obs in obs_collection: 

124 cutout = stacked.cutout(obs.pointing_radec, width="6.5 deg") 

125 dataset = maker.run(cutout, obs) 

126 dataset = maker_safe_mask.run(dataset, obs) 

127 stacked.stack(dataset) 

128 unstacked.append(dataset) 

129 

130n_bin_energy_acceptance = int( 

131 (np.log10(e_max.to_value(u.TeV)) - np.log10(e_min.to_value(u.TeV))) * n_bin_per_decade_acceptance 

132) 

133energyAxisAcceptance = MapAxis.from_edges( 

134 np.logspace(np.log10(e_min.to_value(u.TeV)), np.log10(e_max.to_value(u.TeV)), 1 + n_bin_energy_acceptance), 

135 unit="TeV", 

136 name="energy", 

137 interp="log", 

138) 

139n_bin_offset_acceptance = int(fov_observation.to_value(u.deg) / offset_bin_size_acceptance.to_value(u.deg)) 

140offsetAxisAcceptance = MapAxis.from_edges( 

141 np.linspace(0.0, fov_observation.to_value(u.deg), 1 + n_bin_offset_acceptance), 

142 unit="deg", 

143 name="offset", 

144 interp="lin", 

145) 

146 

147background_creator = RadialAcceptanceMapCreator( 

148 energyAxisAcceptance, 

149 offsetAxisAcceptance, 

150 exclude_regions=[ 

151 exclude_region, 

152 ], 

153 oversample_map=10, 

154) 

155background = background_creator.create_radial_acceptance_map_per_observation(obs_collection) 

156# background[list(background.keys())[0]].peek() 

157for obs_id in background.keys(): 

158 hdu_background = background[obs_id].to_table_hdu() 

159 hdu_background.writeto( 

160 os.path.join(path_background, filename_output + "_" + str(obs_id).zfill(5) + "_background.fits"), 

161 overwrite=True, 

162 ) 

163 

164data_store.hdu_table.remove_rows(data_store.hdu_table["HDU_TYPE"] == "bkg") 

165 

166for obs_id in np.unique(data_store.hdu_table["OBS_ID"]): 

167 data_store.hdu_table.add_row( 

168 { 

169 "OBS_ID": obs_id, 

170 "HDU_TYPE": "bkg", 

171 "HDU_CLASS": "bkg_2d", 

172 "FILE_DIR": "", 

173 "FILE_NAME": os.path.join(path_background, filename_output + "_" + str(obs_id) + "_background.fits"), 

174 "HDU_NAME": "BACKGROUND", 

175 "SIZE": hdu_background.size, 

176 } 

177 ) 

178 

179data_store.hdu_table = data_store.hdu_table.copy() 

180obs_collection = data_store.get_observations(obs_ids, required_irf=None) 

181 

182stacked = MapDataset.create(geom=geom) 

183unstacked = Datasets() 

184maker = MapDatasetMaker(selection=["counts", "background"]) 

185maker_safe_mask = SafeMaskMaker(methods=["offset-max"], offset_max=fov_observation) 

186 

187for obs in obs_collection: 

188 cutout = stacked.cutout(obs.pointing_radec, width="6.5 deg") 

189 dataset = maker.run(cutout, obs) 

190 dataset = maker_safe_mask.run(dataset, obs) 

191 stacked.stack(dataset) 

192 unstacked.append(dataset) 

193 

194ring_bkg_maker = RingBackgroundMaker(r_in=r_in, width=width) # , exclusion_mask=exclusion_mask) 

195stacked_ring = ring_bkg_maker.run(stacked.to_image()) 

196estimator = ExcessMapEstimator(correlation_radius, correlate_off=False) 

197lima_maps = estimator.run(stacked_ring) 

198 

199significance_all = lima_maps["sqrt_ts"].data[np.isfinite(lima_maps["sqrt_ts"].data)] 

200significance_background = lima_maps["sqrt_ts"].data[ 

201 np.logical_and(np.isfinite(lima_maps["sqrt_ts"].data), exclusion_mask.data) 

202] 

203 

204bins = np.linspace( 

205 np.min(significance_all), 

206 np.max(significance_all), 

207 num=int((np.max(significance_all) - np.min(significance_all)) * 3), 

208) 

209 

210# Now, fit the off distribution with a Gaussian 

211mu, std = norm.fit(significance_background) 

212x = np.linspace(-8, 8, 50) 

213p = norm.pdf(x, mu, std) 

214 

215plt.figure(figsize=(8, 21)) 

216ax1 = plt.subplot(3, 1, 1, projection=lima_maps["sqrt_ts"].geom.wcs) 

217ax2 = plt.subplot(3, 1, 2, projection=lima_maps["sqrt_ts"].geom.wcs) 

218ax3 = plt.subplot(3, 1, 3) 

219 

220ax2.set_title("Significance map") 

221lima_maps["sqrt_ts"].plot(ax=ax2, add_cbar=True) 

222ax2.scatter( 

223 source_position.ra, 

224 source_position.dec, 

225 transform=ax2.get_transform("world"), 

226 marker="+", 

227 c="red", 

228 label=filename_output, 

229 s=[300], 

230 linewidths=3, 

231) 

232ax2.legend() 

233 

234ax1.set_title("Excess map") 

235lima_maps["npred_excess"].plot(ax=ax1, add_cbar=True) 

236ax1.scatter( 

237 source_position.ra, 

238 source_position.dec, 

239 transform=ax1.get_transform("world"), 

240 marker="+", 

241 c="red", 

242 label=filename_output, 

243 s=[300], 

244 linewidths=3, 

245) 

246ax1.legend() 

247 

248ax3.set_title("Significance distribution") 

249ax3.hist(significance_all, density=True, alpha=0.5, color="red", label="All bins", bins=bins) 

250ax3.hist(significance_background, density=True, alpha=0.5, color="blue", label="Background bins", bins=bins) 

251 

252ax3.plot(x, p, lw=2, color="black") 

253ax3.legend() 

254ax3.set_xlabel("Significance") 

255ax3.set_yscale("log") 

256ax3.set_ylim(1e-5, 1) 

257xmin, xmax = np.min(significance_all), np.max(significance_all) 

258ax3.set_xlim(xmin, xmax) 

259 

260text = text = rf"$\mu$ = {mu:.2f}" "\n" rf"$\sigma$ = {std:.2f}" 

261box_prop = dict(boxstyle="Round", facecolor="white", alpha=0.5) 

262text_prop = dict(fontsize="x-large", bbox=box_prop) 

263# txt = AnchoredText(text, loc=2, transform=ax3.transAxes, prop=text_prop, frameon=False) 

264txt = AnchoredText(text, loc=2, prop=text_prop, frameon=False) 

265ax3.add_artist(txt) 

266 

267plt.savefig(os.path.join(path_plot, f"{filename_output}__sky_map.png"), dpi=300) 

268print(f"Fit results: mu = {mu:.2f}, std = {std:.2f}")