1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst |
||
2 | """HESS Galactic plane survey (HGPS) catalog.""" |
||
3 | import numpy as np |
||
4 | import astropy.units as u |
||
5 | from astropy.coordinates import Angle |
||
6 | from astropy.modeling.models import Gaussian1D |
||
7 | from astropy.table import Table |
||
8 | from gammapy.estimators import FluxPoints |
||
9 | from gammapy.maps import MapAxis, RegionGeom |
||
10 | from gammapy.modeling.models import Model, Models, SkyModel |
||
11 | from gammapy.utils.interpolation import ScaledRegularGridInterpolator |
||
12 | from gammapy.utils.scripts import make_path |
||
13 | from gammapy.utils.table import table_row_to_dict |
||
14 | from .core import SourceCatalog, SourceCatalogObject, format_flux_points_table |
||
15 | |||
16 | __all__ = [ |
||
17 | "SourceCatalogHGPS", |
||
18 | "SourceCatalogLargeScaleHGPS", |
||
19 | "SourceCatalogObjectHGPS", |
||
20 | "SourceCatalogObjectHGPSComponent", |
||
21 | ] |
||
22 | |||
23 | # Flux factor, used for printing |
||
24 | FF = 1e-12 |
||
25 | |||
26 | # Multiplicative factor to go from cm^-2 s^-1 to % Crab for integral flux > 1 TeV |
||
27 | # Here we use the same Crab reference that's used in the HGPS paper |
||
28 | # CRAB = crab_integral_flux(energy_min=1, reference='hess_ecpl') |
||
29 | FLUX_TO_CRAB = 100 / 2.26e-11 |
||
30 | FLUX_TO_CRAB_DIFF = 100 / 3.5060459323111307e-11 |
||
31 | |||
32 | |||
33 | class SourceCatalogObjectHGPSComponent(SourceCatalogObject): |
||
34 | """One Gaussian component from the HGPS catalog. |
||
35 | |||
36 | See Also |
||
37 | -------- |
||
38 | SourceCatalogHGPS, SourceCatalogObjectHGPS |
||
39 | """ |
||
40 | |||
41 | _source_name_key = "Component_ID" |
||
42 | |||
43 | def __init__(self, data): |
||
44 | self.data = data |
||
45 | |||
46 | def __repr__(self): |
||
47 | return f"{self.__class__.__name__}({self.name!r})" |
||
48 | |||
49 | def __str__(self): |
||
50 | """Pretty-print source data""" |
||
51 | d = self.data |
||
52 | ss = "Component {}:\n".format(d["Component_ID"]) |
||
53 | fmt = "{:<20s} : {:8.3f} +/- {:.3f} deg\n" |
||
54 | ss += fmt.format("GLON", d["GLON"].value, d["GLON_Err"].value) |
||
55 | ss += fmt.format("GLAT", d["GLAT"].value, d["GLAT_Err"].value) |
||
56 | fmt = "{:<20s} : {:.3f} +/- {:.3f} deg\n" |
||
57 | ss += fmt.format("Size", d["Size"].value, d["Size_Err"].value) |
||
58 | val, err = d["Flux_Map"].value, d["Flux_Map_Err"].value |
||
59 | fmt = "{:<20s} : ({:.2f} +/- {:.2f}) x 10^-12 cm^-2 s^-1 = ({:.1f} +/- {:.1f}) % Crab" |
||
60 | ss += fmt.format( |
||
61 | "Flux (>1 TeV)", val / FF, err / FF, val * FLUX_TO_CRAB, err * FLUX_TO_CRAB |
||
62 | ) |
||
63 | return ss |
||
64 | |||
65 | @property |
||
66 | def name(self): |
||
67 | """Source name (str)""" |
||
68 | return self.data[self._source_name_key] |
||
69 | |||
70 | def spatial_model(self): |
||
71 | """Component spatial model (`~gammapy.modeling.models.GaussianSpatialModel`).""" |
||
72 | d = self.data |
||
73 | tag = "GaussianSpatialModel" |
||
74 | pars = { |
||
75 | "lon_0": d["GLON"], |
||
76 | "lat_0": d["GLAT"], |
||
77 | "sigma": d["Size"], |
||
78 | "frame": "galactic", |
||
79 | } |
||
80 | errs = {"lon_0": d["GLON_Err"], "lat_0": d["GLAT_Err"], "sigma": d["Size_Err"]} |
||
81 | model = Model.create(tag, "spatial", **pars) |
||
82 | |||
83 | for name, value in errs.items(): |
||
84 | model.parameters[name].error = value |
||
85 | |||
86 | return model |
||
87 | |||
88 | |||
89 | class SourceCatalogObjectHGPS(SourceCatalogObject): |
||
90 | """One object from the HGPS catalog. |
||
91 | |||
92 | The catalog is represented by `SourceCatalogHGPS`. |
||
93 | Examples are given there. |
||
94 | """ |
||
95 | |||
96 | def __repr__(self): |
||
97 | return f"{self.__class__.__name__}({self.name!r})" |
||
98 | |||
99 | def __str__(self): |
||
100 | return self.info() |
||
101 | |||
102 | @property |
||
103 | def flux_points(self): |
||
104 | """Flux points (`~gammapy.estimators.FluxPoints`).""" |
||
105 | reference_model = SkyModel(spectral_model=self.spectral_model(), name=self.name) |
||
106 | return FluxPoints.from_table( |
||
107 | self.flux_points_table, |
||
108 | reference_model=reference_model, |
||
109 | ) |
||
110 | |||
111 | def info(self, info="all"): |
||
112 | """Info string. |
||
113 | |||
114 | Parameters |
||
115 | ---------- |
||
116 | info : {'all', 'basic', 'map', 'spec', 'flux_points', 'components', 'associations', 'id'} |
||
117 | Comma separated list of options |
||
118 | """ |
||
119 | if info == "all": |
||
120 | info = "basic,associations,id,map,spec,flux_points,components" |
||
121 | |||
122 | ss = "" |
||
123 | ops = info.split(",") |
||
124 | if "basic" in ops: |
||
125 | ss += self._info_basic() |
||
126 | if "map" in ops: |
||
127 | ss += self._info_map() |
||
128 | if "spec" in ops: |
||
129 | ss += self._info_spec() |
||
130 | if "flux_points" in ops: |
||
131 | ss += self._info_flux_points() |
||
132 | if "components" in ops and hasattr(self, "components"): |
||
133 | ss += self._info_components() |
||
134 | if "associations" in ops: |
||
135 | ss += self._info_associations() |
||
136 | if "id" in ops and hasattr(self, "identification_info"): |
||
137 | ss += self._info_id() |
||
138 | return ss |
||
139 | |||
140 | def _info_basic(self): |
||
141 | """Print basic info.""" |
||
142 | d = self.data |
||
143 | ss = "\n*** Basic info ***\n\n" |
||
144 | ss += "Catalog row index (zero-based) : {}\n".format(self.row_index) |
||
145 | ss += "{:<20s} : {}\n".format("Source name", self.name) |
||
146 | |||
147 | ss += "{:<20s} : {}\n".format("Analysis reference", d["Analysis_Reference"]) |
||
148 | ss += "{:<20s} : {}\n".format("Source class", d["Source_Class"]) |
||
149 | ss += "{:<20s} : {}\n".format("Identified object", d["Identified_Object"]) |
||
150 | ss += "{:<20s} : {}\n".format("Gamma-Cat id", d["Gamma_Cat_Source_ID"]) |
||
151 | ss += "\n" |
||
152 | return ss |
||
153 | |||
154 | def _info_associations(self): |
||
155 | ss = "\n*** Source associations info ***\n\n" |
||
156 | lines = self.associations.pformat(max_width=-1, max_lines=-1) |
||
157 | ss += "\n".join(lines) |
||
158 | return ss + "\n" |
||
159 | |||
160 | def _info_id(self): |
||
161 | ss = "\n*** Source identification info ***\n\n" |
||
162 | ss += "\n".join(f"{k}: {v}" for k, v in self.identification_info.items()) |
||
163 | return ss + "\n" |
||
164 | |||
165 | def _info_map(self): |
||
166 | """Print info from map analysis.""" |
||
167 | d = self.data |
||
168 | ss = "\n*** Info from map analysis ***\n\n" |
||
169 | |||
170 | ra_str = Angle(d["RAJ2000"], "deg").to_string(unit="hour", precision=0) |
||
171 | dec_str = Angle(d["DEJ2000"], "deg").to_string(unit="deg", precision=0) |
||
172 | ss += "{:<20s} : {:8.3f} = {}\n".format("RA", d["RAJ2000"], ra_str) |
||
173 | ss += "{:<20s} : {:8.3f} = {}\n".format("DEC", d["DEJ2000"], dec_str) |
||
174 | |||
175 | ss += "{:<20s} : {:8.3f} +/- {:.3f} deg\n".format( |
||
176 | "GLON", d["GLON"].value, d["GLON_Err"].value |
||
177 | ) |
||
178 | ss += "{:<20s} : {:8.3f} +/- {:.3f} deg\n".format( |
||
179 | "GLAT", d["GLAT"].value, d["GLAT_Err"].value |
||
180 | ) |
||
181 | |||
182 | ss += "{:<20s} : {:.3f}\n".format("Position Error (68%)", d["Pos_Err_68"]) |
||
183 | ss += "{:<20s} : {:.3f}\n".format("Position Error (95%)", d["Pos_Err_95"]) |
||
184 | |||
185 | ss += "{:<20s} : {:.0f}\n".format("ROI number", d["ROI_Number"]) |
||
186 | ss += "{:<20s} : {}\n".format("Spatial model", d["Spatial_Model"]) |
||
187 | ss += "{:<20s} : {}\n".format("Spatial components", d["Components"]) |
||
188 | |||
189 | ss += "{:<20s} : {:.1f}\n".format("TS", d["Sqrt_TS"] ** 2) |
||
190 | ss += "{:<20s} : {:.1f}\n".format("sqrt(TS)", d["Sqrt_TS"]) |
||
191 | |||
192 | ss += "{:<20s} : {:.3f} +/- {:.3f} (UL: {:.3f}) deg\n".format( |
||
193 | "Size", d["Size"].value, d["Size_Err"].value, d["Size_UL"].value |
||
194 | ) |
||
195 | |||
196 | ss += "{:<20s} : {:.3f}\n".format("R70", d["R70"]) |
||
197 | ss += "{:<20s} : {:.3f}\n".format("RSpec", d["RSpec"]) |
||
198 | |||
199 | ss += "{:<20s} : {:.1f}\n".format("Total model excess", d["Excess_Model_Total"]) |
||
200 | ss += "{:<20s} : {:.1f}\n".format("Excess in RSpec", d["Excess_RSpec"]) |
||
201 | ss += "{:<20s} : {:.1f}\n".format( |
||
202 | "Model Excess in RSpec", d["Excess_RSpec_Model"] |
||
203 | ) |
||
204 | ss += "{:<20s} : {:.1f}\n".format("Background in RSpec", d["Background_RSpec"]) |
||
205 | |||
206 | ss += "{:<20s} : {:.1f} hours\n".format("Livetime", d["Livetime"].value) |
||
207 | |||
208 | ss += "{:<20s} : {:.2f}\n".format("Energy threshold", d["Energy_Threshold"]) |
||
209 | |||
210 | val, err = d["Flux_Map"].value, d["Flux_Map_Err"].value |
||
211 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: 501 |
||
212 | "Source flux (>1 TeV)", |
||
213 | val / FF, |
||
214 | err / FF, |
||
215 | val * FLUX_TO_CRAB, |
||
216 | err * FLUX_TO_CRAB, |
||
217 | ) |
||
218 | |||
219 | ss += "\nFluxes in RSpec (> 1 TeV):\n" |
||
220 | |||
221 | ss += "{:<30s} : {:.3f} x 10^-12 cm^-2 s^-1 = {:5.2f} % Crab\n".format( |
||
222 | "Map measurement", |
||
223 | d["Flux_Map_RSpec_Data"].value / FF, |
||
224 | d["Flux_Map_RSpec_Data"].value * FLUX_TO_CRAB, |
||
225 | ) |
||
226 | |||
227 | ss += "{:<30s} : {:.3f} x 10^-12 cm^-2 s^-1 = {:5.2f} % Crab\n".format( |
||
228 | "Source model", |
||
229 | d["Flux_Map_RSpec_Source"].value / FF, |
||
230 | d["Flux_Map_RSpec_Source"].value * FLUX_TO_CRAB, |
||
231 | ) |
||
232 | |||
233 | ss += "{:<30s} : {:.3f} x 10^-12 cm^-2 s^-1 = {:5.2f} % Crab\n".format( |
||
234 | "Other component model", |
||
235 | d["Flux_Map_RSpec_Other"].value / FF, |
||
236 | d["Flux_Map_RSpec_Other"].value * FLUX_TO_CRAB, |
||
237 | ) |
||
238 | |||
239 | ss += "{:<30s} : {:.3f} x 10^-12 cm^-2 s^-1 = {:5.2f} % Crab\n".format( |
||
240 | "Large scale component model", |
||
241 | d["Flux_Map_RSpec_LS"].value / FF, |
||
242 | d["Flux_Map_RSpec_LS"].value * FLUX_TO_CRAB, |
||
243 | ) |
||
244 | |||
245 | ss += "{:<30s} : {:.3f} x 10^-12 cm^-2 s^-1 = {:5.2f} % Crab\n".format( |
||
246 | "Total model", |
||
247 | d["Flux_Map_RSpec_Total"].value / FF, |
||
248 | d["Flux_Map_RSpec_Total"].value * FLUX_TO_CRAB, |
||
249 | ) |
||
250 | |||
251 | ss += "{:<35s} : {:5.1f} %\n".format( |
||
252 | "Containment in RSpec", 100 * d["Containment_RSpec"] |
||
253 | ) |
||
254 | ss += "{:<35s} : {:5.1f} %\n".format( |
||
255 | "Contamination in RSpec", 100 * d["Contamination_RSpec"] |
||
256 | ) |
||
257 | label, val = ( |
||
258 | "Flux correction (RSpec -> Total)", |
||
259 | 100 * d["Flux_Correction_RSpec_To_Total"], |
||
260 | ) |
||
261 | ss += f"{label:<35s} : {val:5.1f} %\n" |
||
262 | label, val = ( |
||
263 | "Flux correction (Total -> RSpec)", |
||
264 | 100 * (1 / d["Flux_Correction_RSpec_To_Total"]), |
||
265 | ) |
||
266 | ss += f"{label:<35s} : {val:5.1f} %\n" |
||
267 | |||
268 | return ss |
||
269 | |||
270 | def _info_spec(self): |
||
271 | """Print info from spectral analysis.""" |
||
272 | d = self.data |
||
273 | ss = "\n*** Info from spectral analysis ***\n\n" |
||
274 | |||
275 | ss += "{:<20s} : {:.1f} hours\n".format("Livetime", d["Livetime_Spec"].value) |
||
276 | |||
277 | lo = d["Energy_Range_Spec_Min"].value |
||
278 | hi = d["Energy_Range_Spec_Max"].value |
||
279 | ss += "{:<20s} : {:.2f} to {:.2f} TeV\n".format("Energy range:", lo, hi) |
||
280 | |||
281 | ss += "{:<20s} : {:.1f}\n".format("Background", d["Background_Spec"]) |
||
282 | ss += "{:<20s} : {:.1f}\n".format("Excess", d["Excess_Spec"]) |
||
283 | ss += "{:<20s} : {}\n".format("Spectral model", d["Spectral_Model"]) |
||
284 | |||
285 | val = d["TS_ECPL_over_PL"] |
||
286 | ss += "{:<20s} : {:.1f}\n".format("TS ECPL over PL", val) |
||
287 | |||
288 | val = d["Flux_Spec_Int_1TeV"].value |
||
289 | err = d["Flux_Spec_Int_1TeV_Err"].value |
||
290 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: E501 |
||
291 | "Best-fit model flux(> 1 TeV)", |
||
292 | val / FF, |
||
293 | err / FF, |
||
294 | val * FLUX_TO_CRAB, |
||
295 | err * FLUX_TO_CRAB, |
||
296 | ) |
||
297 | |||
298 | val = d["Flux_Spec_Energy_1_10_TeV"].value |
||
299 | err = d["Flux_Spec_Energy_1_10_TeV_Err"].value |
||
300 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 erg cm^-2 s^-1\n".format( |
||
301 | "Best-fit model energy flux(1 to 10 TeV)", val / FF, err / FF |
||
302 | ) |
||
303 | |||
304 | ss += self._info_spec_pl() |
||
305 | ss += self._info_spec_ecpl() |
||
306 | |||
307 | return ss |
||
308 | |||
309 | def _info_spec_pl(self): |
||
310 | d = self.data |
||
311 | ss = "{:<20s} : {:.2f}\n".format("Pivot energy", d["Energy_Spec_PL_Pivot"]) |
||
312 | |||
313 | val = d["Flux_Spec_PL_Diff_Pivot"].value |
||
314 | err = d["Flux_Spec_PL_Diff_Pivot_Err"].value |
||
315 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 TeV^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: E501 |
||
316 | "Flux at pivot energy", |
||
317 | val / FF, |
||
318 | err / FF, |
||
319 | val * FLUX_TO_CRAB, |
||
320 | err * FLUX_TO_CRAB_DIFF, |
||
321 | ) |
||
322 | |||
323 | val = d["Flux_Spec_PL_Int_1TeV"].value |
||
324 | err = d["Flux_Spec_PL_Int_1TeV_Err"].value |
||
325 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: E501 |
||
326 | "PL Flux(> 1 TeV)", |
||
327 | val / FF, |
||
328 | err / FF, |
||
329 | val * FLUX_TO_CRAB, |
||
330 | err * FLUX_TO_CRAB, |
||
331 | ) |
||
332 | |||
333 | val = d["Flux_Spec_PL_Diff_1TeV"].value |
||
334 | err = d["Flux_Spec_PL_Diff_1TeV_Err"].value |
||
335 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 TeV^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: E501 |
||
336 | "PL Flux(@ 1 TeV)", |
||
337 | val / FF, |
||
338 | err / FF, |
||
339 | val * FLUX_TO_CRAB, |
||
340 | err * FLUX_TO_CRAB_DIFF, |
||
341 | ) |
||
342 | |||
343 | val = d["Index_Spec_PL"] |
||
344 | err = d["Index_Spec_PL_Err"] |
||
345 | ss += "{:<20s} : {:.2f} +/- {:.2f}\n".format("PL Index", val, err) |
||
346 | |||
347 | return ss |
||
348 | |||
349 | def _info_spec_ecpl(self): |
||
350 | d = self.data |
||
351 | ss = "" |
||
352 | # ss = '{:<20s} : {:.1f}\n'.format('Pivot energy', d['Energy_Spec_ECPL_Pivot']) |
||
353 | |||
354 | val = d["Flux_Spec_ECPL_Diff_1TeV"].value |
||
355 | err = d["Flux_Spec_ECPL_Diff_1TeV_Err"].value |
||
356 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 TeV^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: E501 |
||
357 | "ECPL Flux(@ 1 TeV)", |
||
358 | val / FF, |
||
359 | err / FF, |
||
360 | val * FLUX_TO_CRAB, |
||
361 | err * FLUX_TO_CRAB_DIFF, |
||
362 | ) |
||
363 | |||
364 | val = d["Flux_Spec_ECPL_Int_1TeV"].value |
||
365 | err = d["Flux_Spec_ECPL_Int_1TeV_Err"].value |
||
366 | ss += "{:<20s} : ({:.3f} +/- {:.3f}) x 10^-12 cm^-2 s^-1 = ({:.2f} +/- {:.2f}) % Crab\n".format( # noqa: E501 |
||
367 | "ECPL Flux(> 1 TeV)", |
||
368 | val / FF, |
||
369 | err / FF, |
||
370 | val * FLUX_TO_CRAB, |
||
371 | err * FLUX_TO_CRAB, |
||
372 | ) |
||
373 | |||
374 | val = d["Index_Spec_ECPL"] |
||
375 | err = d["Index_Spec_ECPL_Err"] |
||
376 | ss += "{:<20s} : {:.2f} +/- {:.2f}\n".format("ECPL Index", val, err) |
||
377 | |||
378 | val = d["Lambda_Spec_ECPL"].value |
||
379 | err = d["Lambda_Spec_ECPL_Err"].value |
||
380 | ss += "{:<20s} : {:.3f} +/- {:.3f} TeV^-1\n".format("ECPL Lambda", val, err) |
||
381 | |||
382 | # Use Gaussian analytical error propagation, |
||
383 | # tested against the uncertainties package |
||
384 | err = err / val**2 |
||
385 | val = 1.0 / val |
||
386 | |||
387 | ss += "{:<20s} : {:.2f} +/- {:.2f} TeV\n".format("ECPL E_cut", val, err) |
||
388 | |||
389 | return ss |
||
390 | |||
391 | def _info_flux_points(self): |
||
392 | """Print flux point results""" |
||
393 | d = self.data |
||
394 | ss = "\n*** Flux points info ***\n\n" |
||
395 | ss += "Number of flux points: {}\n".format(d["N_Flux_Points"]) |
||
396 | ss += "Flux points table: \n\n" |
||
397 | lines = format_flux_points_table(self.flux_points_table).pformat( |
||
398 | max_width=-1, max_lines=-1 |
||
399 | ) |
||
400 | ss += "\n".join(lines) |
||
401 | return ss + "\n" |
||
402 | |||
403 | def _info_components(self): |
||
404 | """Print info about the components.""" |
||
405 | ss = "\n*** Gaussian component info ***\n\n" |
||
406 | ss += "Number of components: {}\n".format(len(self.components)) |
||
407 | ss += "{:<20s} : {}\n\n".format("Spatial components", self.data["Components"]) |
||
408 | |||
409 | for component in self.components: |
||
410 | ss += str(component) |
||
411 | ss += "\n\n" |
||
412 | |||
413 | return ss |
||
414 | |||
415 | @property |
||
416 | def energy_range(self): |
||
417 | """Spectral model energy range (`~astropy.units.Quantity` with length 2).""" |
||
418 | energy_min, energy_max = ( |
||
419 | self.data["Energy_Range_Spec_Min"], |
||
420 | self.data["Energy_Range_Spec_Max"], |
||
421 | ) |
||
422 | |||
423 | if np.isnan(energy_min): |
||
424 | energy_min = u.Quantity(0.2, "TeV") |
||
425 | |||
426 | if np.isnan(energy_max): |
||
427 | energy_max = u.Quantity(50, "TeV") |
||
428 | |||
429 | return u.Quantity([energy_min, energy_max], "TeV") |
||
430 | |||
431 | def spectral_model(self, which="best"): |
||
432 | """Spectral model (`~gammapy.modeling.models.SpectralModel`). |
||
433 | |||
434 | One of the following models (given by ``Spectral_Model`` in the catalog): |
||
435 | |||
436 | - ``PL`` : `~gammapy.modeling.models.PowerLawSpectralModel` |
||
437 | - ``ECPL`` : `~gammapy.modeling.models.ExpCutoffPowerLawSpectralModel` |
||
438 | |||
439 | Parameters |
||
440 | ---------- |
||
441 | which : {'best', 'pl', 'ecpl'} |
||
442 | Which spectral model |
||
443 | """ |
||
444 | data = self.data |
||
445 | |||
446 | if which == "best": |
||
447 | spec_type = self.data["Spectral_Model"].strip().lower() |
||
448 | elif which in {"pl", "ecpl"}: |
||
449 | spec_type = which |
||
450 | else: |
||
451 | raise ValueError(f"Invalid selection: which = {which!r}") |
||
452 | |||
453 | View Code Duplication | if spec_type == "pl": |
|
0 ignored issues
–
show
Duplication
introduced
by
Loading history...
|
|||
454 | tag = "PowerLawSpectralModel" |
||
455 | pars = { |
||
456 | "index": data["Index_Spec_PL"], |
||
457 | "amplitude": data["Flux_Spec_PL_Diff_Pivot"], |
||
458 | "reference": data["Energy_Spec_PL_Pivot"], |
||
459 | } |
||
460 | errs = { |
||
461 | "amplitude": data["Flux_Spec_PL_Diff_Pivot_Err"], |
||
462 | "index": data["Index_Spec_PL_Err"], |
||
463 | } |
||
464 | elif spec_type == "ecpl": |
||
465 | tag = "ExpCutoffPowerLawSpectralModel" |
||
466 | pars = { |
||
467 | "index": data["Index_Spec_ECPL"], |
||
468 | "amplitude": data["Flux_Spec_ECPL_Diff_Pivot"], |
||
469 | "reference": data["Energy_Spec_ECPL_Pivot"], |
||
470 | "lambda_": data["Lambda_Spec_ECPL"], |
||
471 | } |
||
472 | errs = { |
||
473 | "index": data["Index_Spec_ECPL_Err"], |
||
474 | "amplitude": data["Flux_Spec_ECPL_Diff_Pivot_Err"], |
||
475 | "lambda_": data["Lambda_Spec_ECPL_Err"], |
||
476 | } |
||
477 | else: |
||
478 | raise ValueError(f"Invalid spec_type: {spec_type}") |
||
479 | |||
480 | model = Model.create(tag, "spectral", **pars) |
||
481 | errs["reference"] = 0 * u.TeV |
||
482 | |||
483 | for name, value in errs.items(): |
||
484 | model.parameters[name].error = value |
||
485 | |||
486 | return model |
||
487 | |||
488 | def spatial_model(self): |
||
489 | """Spatial model (`~gammapy.modeling.models.SpatialModel`). |
||
490 | |||
491 | One of the following models (given by ``Spatial_Model`` in the catalog): |
||
492 | |||
493 | - ``Point-Like`` or has a size upper limit : `~gammapy.modeling.models.PointSpatialModel` |
||
494 | - ``Gaussian``: `~gammapy.modeling.models.GaussianSpatialModel` |
||
495 | - ``2-Gaussian`` or ``3-Gaussian``: composite model (using ``+`` with Gaussians) |
||
496 | - ``Shell``: `~gammapy.modeling.models.ShellSpatialModel` |
||
497 | """ |
||
498 | d = self.data |
||
499 | pars = {"lon_0": d["GLON"], "lat_0": d["GLAT"], "frame": "galactic"} |
||
500 | errs = {"lon_0": d["GLON_Err"], "lat_0": d["GLAT_Err"]} |
||
501 | |||
502 | spatial_type = self.data["Spatial_Model"] |
||
503 | |||
504 | if spatial_type == "Point-Like": |
||
505 | tag = "PointSpatialModel" |
||
506 | elif spatial_type == "Gaussian": |
||
507 | tag = "GaussianSpatialModel" |
||
508 | pars["sigma"] = d["Size"] |
||
509 | errs["sigma"] = d["Size_Err"] |
||
510 | elif spatial_type in {"2-Gaussian", "3-Gaussian"}: |
||
511 | raise ValueError("For Gaussian or Multi-Gaussian models, use sky_model()!") |
||
512 | elif spatial_type == "Shell": |
||
513 | # HGPS contains no information on shell width |
||
514 | # Here we assume a 5% shell width for all shells. |
||
515 | tag = "ShellSpatialModel" |
||
516 | pars["radius"] = 0.95 * d["Size"] |
||
517 | pars["width"] = d["Size"] - pars["radius"] |
||
518 | errs["radius"] = d["Size_Err"] |
||
519 | else: |
||
520 | raise ValueError(f"Invalid spatial_type: {spatial_type}") |
||
521 | |||
522 | model = Model.create(tag, "spatial", **pars) |
||
523 | for name, value in errs.items(): |
||
524 | model.parameters[name].error = value |
||
525 | return model |
||
526 | |||
527 | def sky_model(self, which="best"): |
||
528 | """Source sky model. |
||
529 | |||
530 | Parameters |
||
531 | ---------- |
||
532 | which : {'best', 'pl', 'ecpl'} |
||
533 | Which spectral model |
||
534 | |||
535 | Returns |
||
536 | ------- |
||
537 | sky_model : `~gammapy.modeling.models.Models` |
||
538 | Models of the catalog object. |
||
539 | """ |
||
540 | |||
541 | models = self.components_models(which=which) |
||
542 | if len(models) > 1: |
||
543 | geom = self._get_components_geom(models) |
||
544 | return models.to_template_sky_model(geom=geom, name=self.name) |
||
545 | else: |
||
546 | return models[0] |
||
547 | |||
548 | def components_models(self, which="best", linked=False): |
||
549 | """Models of the source components. |
||
550 | |||
551 | Parameters |
||
552 | ---------- |
||
553 | which : {'best', 'pl', 'ecpl'} |
||
554 | Which spectral model |
||
555 | |||
556 | linked : bool |
||
557 | Each sub-component of a source is given as a different `SkyModel` |
||
558 | If True the spectral parameters except the mormalisation are linked. |
||
559 | Default is False |
||
560 | |||
561 | Returns |
||
562 | ------- |
||
563 | sky_model : `~gammapy.modeling.models.Models` |
||
564 | Models of the catalog object. |
||
565 | """ |
||
566 | |||
567 | spatial_type = self.data["Spatial_Model"] |
||
568 | missing_size = ( |
||
569 | spatial_type == "Gaussian" and self.spatial_model().sigma.value == 0 |
||
570 | ) |
||
571 | if spatial_type in {"2-Gaussian", "3-Gaussian"} or missing_size: |
||
572 | models = [] |
||
573 | spectral_model = self.spectral_model(which=which) |
||
574 | for component in self.components: |
||
575 | spec_component = spectral_model.copy() |
||
576 | weight = component.data["Flux_Map"] / self.data["Flux_Map"] |
||
577 | spec_component.parameters["amplitude"].value *= weight |
||
578 | if linked: |
||
579 | for name in spec_component.parameters.names: |
||
580 | if name not in ["norm", "amplitude"]: |
||
581 | spec_component.__dict__[name] = spectral_model.parameters[ |
||
582 | name |
||
583 | ] |
||
584 | model = SkyModel( |
||
585 | spatial_model=component.spatial_model(), |
||
586 | spectral_model=spec_component, |
||
587 | name=component.name, |
||
588 | ) |
||
589 | models.append(model) |
||
590 | else: |
||
591 | models = [ |
||
592 | SkyModel( |
||
593 | spatial_model=self.spatial_model(), |
||
594 | spectral_model=self.spectral_model(which=which), |
||
595 | name=self.name, |
||
596 | ) |
||
597 | ] |
||
598 | return Models(models) |
||
599 | |||
600 | @staticmethod |
||
601 | def _get_components_geom(models): |
||
602 | energy_axis = MapAxis.from_energy_bounds( |
||
603 | "100 GeV", "100 TeV", nbin=10, per_decade=True, name="energy_true" |
||
604 | ) |
||
605 | regions = [m.spatial_model.evaluation_region for m in models] |
||
606 | geom = RegionGeom.from_regions( |
||
607 | regions, binsz_wcs="0.02 deg", axes=[energy_axis] |
||
608 | ) |
||
609 | return geom.to_wcs_geom() |
||
610 | |||
611 | @property |
||
612 | def flux_points_table(self): |
||
613 | """Flux points table (`~astropy.table.Table`).""" |
||
614 | table = Table() |
||
615 | table.meta["sed_type_init"] = "dnde" |
||
616 | table.meta["n_sigma_ul"] = 2 |
||
617 | table.meta["n_sigma"] = 1 |
||
618 | table.meta["sqrt_ts_threshold_ul"] = 1 |
||
619 | mask = ~np.isnan(self.data["Flux_Points_Energy"]) |
||
620 | |||
621 | table["e_ref"] = self.data["Flux_Points_Energy"][mask] |
||
622 | table["e_min"] = self.data["Flux_Points_Energy_Min"][mask] |
||
623 | table["e_max"] = self.data["Flux_Points_Energy_Max"][mask] |
||
624 | |||
625 | table["dnde"] = self.data["Flux_Points_Flux"][mask] |
||
626 | table["dnde_errn"] = self.data["Flux_Points_Flux_Err_Lo"][mask] |
||
627 | table["dnde_errp"] = self.data["Flux_Points_Flux_Err_Hi"][mask] |
||
628 | table["dnde_ul"] = self.data["Flux_Points_Flux_UL"][mask] |
||
629 | table["is_ul"] = self.data["Flux_Points_Flux_Is_UL"][mask].astype("bool") |
||
630 | return table |
||
631 | |||
632 | |||
633 | class SourceCatalogHGPS(SourceCatalog): |
||
634 | """HESS Galactic plane survey (HGPS) source catalog. |
||
635 | |||
636 | Reference: https://www.mpi-hd.mpg.de/hfm/HESS/hgps/ |
||
637 | |||
638 | One source is represented by `SourceCatalogObjectHGPS`. |
||
639 | |||
640 | Examples |
||
641 | -------- |
||
642 | Let's assume you have downloaded the HGPS catalog FITS file, e.g. via: |
||
643 | |||
644 | .. code-block:: bash |
||
645 | |||
646 | curl -O https://www.mpi-hd.mpg.de/hfm/HESS/hgps/data/hgps_catalog_v1.fits.gz |
||
647 | |||
648 | Then you can load it up like this: |
||
649 | |||
650 | >>> import matplotlib.pyplot as plt |
||
651 | >>> from gammapy.catalog import SourceCatalogHGPS |
||
652 | >>> filename = '$GAMMAPY_DATA/catalogs/hgps_catalog_v1.fits.gz' |
||
653 | >>> cat = SourceCatalogHGPS(filename) |
||
654 | |||
655 | Access a source by name: |
||
656 | |||
657 | >>> source = cat['HESS J1843-033'] |
||
658 | >>> print(source) |
||
659 | <BLANKLINE> |
||
660 | *** Basic info *** |
||
661 | <BLANKLINE> |
||
662 | Catalog row index (zero-based) : 64 |
||
663 | Source name : HESS J1843-033 |
||
664 | Analysis reference : HGPS |
||
665 | Source class : Unid |
||
666 | Identified object : -- |
||
667 | Gamma-Cat id : 126 |
||
668 | <BLANKLINE> |
||
669 | <BLANKLINE> |
||
670 | *** Info from map analysis *** |
||
671 | <BLANKLINE> |
||
672 | RA : 280.952 deg = 18h43m48s |
||
673 | DEC : -3.554 deg = -3d33m15s |
||
674 | GLON : 28.899 +/- 0.072 deg |
||
675 | GLAT : 0.075 +/- 0.036 deg |
||
676 | Position Error (68%) : 0.122 deg |
||
677 | Position Error (95%) : 0.197 deg |
||
678 | ROI number : 3 |
||
679 | Spatial model : 2-Gaussian |
||
680 | Spatial components : HGPSC 083, HGPSC 084 |
||
681 | TS : 256.6 |
||
682 | sqrt(TS) : 16.0 |
||
683 | Size : 0.239 +/- 0.063 (UL: 0.000) deg |
||
684 | R70 : 0.376 deg |
||
685 | RSpec : 0.376 deg |
||
686 | Total model excess : 979.5 |
||
687 | Excess in RSpec : 775.6 |
||
688 | Model Excess in RSpec : 690.2 |
||
689 | Background in RSpec : 1742.4 |
||
690 | Livetime : 41.8 hours |
||
691 | Energy threshold : 0.38 TeV |
||
692 | Source flux (>1 TeV) : (2.882 +/- 0.305) x 10^-12 cm^-2 s^-1 = (12.75 +/- 1.35) % Crab |
||
693 | <BLANKLINE> |
||
694 | Fluxes in RSpec (> 1 TeV): |
||
695 | Map measurement : 2.267 x 10^-12 cm^-2 s^-1 = 10.03 % Crab |
||
696 | Source model : 2.018 x 10^-12 cm^-2 s^-1 = 8.93 % Crab |
||
697 | Other component model : 0.004 x 10^-12 cm^-2 s^-1 = 0.02 % Crab |
||
698 | Large scale component model : 0.361 x 10^-12 cm^-2 s^-1 = 1.60 % Crab |
||
699 | Total model : 2.383 x 10^-12 cm^-2 s^-1 = 10.54 % Crab |
||
700 | Containment in RSpec : 70.0 % |
||
701 | Contamination in RSpec : 15.3 % |
||
702 | Flux correction (RSpec -> Total) : 121.0 % |
||
703 | Flux correction (Total -> RSpec) : 82.7 % |
||
704 | <BLANKLINE> |
||
705 | *** Info from spectral analysis *** |
||
706 | <BLANKLINE> |
||
707 | Livetime : 16.8 hours |
||
708 | Energy range: : 0.22 to 61.90 TeV |
||
709 | Background : 5126.9 |
||
710 | Excess : 980.1 |
||
711 | Spectral model : PL |
||
712 | TS ECPL over PL : -- |
||
713 | Best-fit model flux(> 1 TeV) : (3.043 +/- 0.196) x 10^-12 cm^-2 s^-1 = (13.47 +/- 0.87) % Crab |
||
714 | Best-fit model energy flux(1 to 10 TeV) : (10.918 +/- 0.733) x 10^-12 erg cm^-2 s^-1 |
||
715 | Pivot energy : 1.87 TeV |
||
716 | Flux at pivot energy : (0.914 +/- 0.058) x 10^-12 cm^-2 s^-1 TeV^-1 = (4.04 +/- 0.17) % Crab |
||
717 | PL Flux(> 1 TeV) : (3.043 +/- 0.196) x 10^-12 cm^-2 s^-1 = (13.47 +/- 0.87) % Crab |
||
718 | PL Flux(@ 1 TeV) : (3.505 +/- 0.247) x 10^-12 cm^-2 s^-1 TeV^-1 = (15.51 +/- 0.70) % Crab |
||
719 | PL Index : 2.15 +/- 0.05 |
||
720 | ECPL Flux(@ 1 TeV) : (0.000 +/- 0.000) x 10^-12 cm^-2 s^-1 TeV^-1 = (0.00 +/- 0.00) % Crab |
||
721 | ECPL Flux(> 1 TeV) : (0.000 +/- 0.000) x 10^-12 cm^-2 s^-1 = (0.00 +/- 0.00) % Crab |
||
722 | ECPL Index : -- +/- -- |
||
723 | ECPL Lambda : 0.000 +/- 0.000 TeV^-1 |
||
724 | ECPL E_cut : inf +/- nan TeV |
||
725 | <BLANKLINE> |
||
726 | *** Flux points info *** |
||
727 | <BLANKLINE> |
||
728 | Number of flux points: 6 |
||
729 | Flux points table: |
||
730 | <BLANKLINE> |
||
731 | e_ref e_min e_max dnde dnde_errn dnde_errp dnde_ul is_ul |
||
732 | TeV TeV TeV 1 / (cm2 s TeV) 1 / (cm2 s TeV) 1 / (cm2 s TeV) 1 / (cm2 s TeV) |
||
733 | ------ ------ ------ --------------- --------------- --------------- --------------- ----- |
||
734 | 0.332 0.215 0.511 3.048e-11 6.890e-12 7.010e-12 4.455e-11 False |
||
735 | 0.787 0.511 1.212 5.383e-12 6.655e-13 6.843e-13 6.739e-12 False |
||
736 | 1.957 1.212 3.162 9.160e-13 9.732e-14 1.002e-13 1.120e-12 False |
||
737 | 4.870 3.162 7.499 1.630e-13 2.001e-14 2.097e-14 2.054e-13 False |
||
738 | 12.115 7.499 19.573 1.648e-14 3.124e-15 3.348e-15 2.344e-14 False |
||
739 | 30.142 19.573 46.416 7.777e-16 4.468e-16 5.116e-16 1.883e-15 False |
||
740 | <BLANKLINE> |
||
741 | *** Gaussian component info *** |
||
742 | <BLANKLINE> |
||
743 | Number of components: 2 |
||
744 | Spatial components : HGPSC 083, HGPSC 084 |
||
745 | <BLANKLINE> |
||
746 | Component HGPSC 083: |
||
747 | GLON : 29.047 +/- 0.026 deg |
||
748 | GLAT : 0.244 +/- 0.027 deg |
||
749 | Size : 0.125 +/- 0.021 deg |
||
750 | Flux (>1 TeV) : (1.34 +/- 0.36) x 10^-12 cm^-2 s^-1 = (5.9 +/- 1.6) % Crab |
||
751 | <BLANKLINE> |
||
752 | Component HGPSC 084: |
||
753 | GLON : 28.770 +/- 0.059 deg |
||
754 | GLAT : -0.073 +/- 0.069 deg |
||
755 | Size : 0.229 +/- 0.046 deg |
||
756 | Flux (>1 TeV) : (1.54 +/- 0.47) x 10^-12 cm^-2 s^-1 = (6.8 +/- 2.1) % Crab |
||
757 | <BLANKLINE> |
||
758 | <BLANKLINE> |
||
759 | *** Source associations info *** |
||
760 | <BLANKLINE> |
||
761 | Source_Name Association_Catalog Association_Name Separation |
||
762 | deg |
||
763 | ---------------- ------------------- --------------------- ---------- |
||
764 | HESS J1843-033 3FGL 3FGL J1843.7-0322 0.178442 |
||
765 | HESS J1843-033 3FGL 3FGL J1844.3-0344 0.242835 |
||
766 | HESS J1843-033 SNR G28.6-0.1 0.330376 |
||
767 | <BLANKLINE> |
||
768 | |||
769 | Access source spectral data and plot it: |
||
770 | |||
771 | >>> ax = plt.subplot() |
||
772 | >>> source.spectral_model().plot(source.energy_range, ax=ax) #doctest:+ELLIPSIS |
||
773 | <AxesSubplot:...xlabel='Energy [TeV]', ylabel='dnde [1 / (cm2 s TeV)]'> |
||
774 | >>> source.spectral_model().plot_error(source.energy_range, ax=ax) #doctest:+ELLIPSIS |
||
775 | <AxesSubplot:...xlabel='Energy [TeV]', ylabel='dnde [1 / (cm2 s TeV)]'> |
||
776 | >>> source.flux_points.plot(ax=ax) #doctest:+ELLIPSIS |
||
777 | <AxesSubplot:...xlabel='Energy [TeV]', ylabel='dnde [1 / (cm2 s TeV)]'> |
||
778 | |||
779 | Gaussian component information can be accessed as well, |
||
780 | either via the source, or via the catalog: |
||
781 | |||
782 | >>> source.components |
||
783 | [SourceCatalogObjectHGPSComponent('HGPSC 083'), SourceCatalogObjectHGPSComponent('HGPSC 084')] |
||
784 | |||
785 | >>> cat.gaussian_component(83) |
||
786 | SourceCatalogObjectHGPSComponent('HGPSC 084') |
||
787 | """ |
||
788 | |||
789 | tag = "hgps" |
||
790 | """Source catalog name (str).""" |
||
791 | |||
792 | description = "H.E.S.S. Galactic plane survey (HGPS) source catalog" |
||
793 | """Source catalog description (str).""" |
||
794 | |||
795 | source_object_class = SourceCatalogObjectHGPS |
||
796 | |||
797 | def __init__( |
||
798 | self, |
||
799 | filename="$GAMMAPY_DATA/catalogs/hgps_catalog_v1.fits.gz", |
||
800 | hdu="HGPS_SOURCES", |
||
801 | ): |
||
802 | filename = make_path(filename) |
||
803 | table = Table.read(filename, hdu=hdu) |
||
804 | |||
805 | source_name_alias = ("Identified_Object",) |
||
806 | super().__init__(table=table, source_name_alias=source_name_alias) |
||
807 | |||
808 | self._table_components = Table.read(filename, hdu="HGPS_GAUSS_COMPONENTS") |
||
809 | self._table_associations = Table.read(filename, hdu="HGPS_ASSOCIATIONS") |
||
810 | self._table_associations["Separation"].format = ".6f" |
||
811 | self._table_identifications = Table.read(filename, hdu="HGPS_IDENTIFICATIONS") |
||
812 | self._table_large_scale_component = Table.read( |
||
813 | filename, hdu="HGPS_LARGE_SCALE_COMPONENT" |
||
814 | ) |
||
815 | |||
816 | @property |
||
817 | def table_components(self): |
||
818 | """Gaussian component table (`~astropy.table.Table`)""" |
||
819 | return self._table_components |
||
820 | |||
821 | @property |
||
822 | def table_associations(self): |
||
823 | """Source association table (`~astropy.table.Table`)""" |
||
824 | return self._table_associations |
||
825 | |||
826 | @property |
||
827 | def table_identifications(self): |
||
828 | """Source identification table (`~astropy.table.Table`)""" |
||
829 | return self._table_identifications |
||
830 | |||
831 | @property |
||
832 | def table_large_scale_component(self): |
||
833 | """Large scale component table (`~astropy.table.Table`)""" |
||
834 | return self._table_large_scale_component |
||
835 | |||
836 | @property |
||
837 | def large_scale_component(self): |
||
838 | """Large scale component model (`~gammapy.catalog.SourceCatalogLargeScaleHGPS`).""" |
||
839 | return SourceCatalogLargeScaleHGPS(self.table_large_scale_component) |
||
840 | |||
841 | def _make_source_object(self, index): |
||
842 | """Make `SourceCatalogObject` for given row index""" |
||
843 | source = super()._make_source_object(index) |
||
844 | |||
845 | if source.data["Components"] != "": |
||
846 | source.components = list(self._get_gaussian_components(source)) |
||
847 | |||
848 | self._attach_association_info(source) |
||
849 | |||
850 | if source.data["Source_Class"] != "Unid": |
||
851 | self._attach_identification_info(source) |
||
852 | |||
853 | return source |
||
854 | |||
855 | def _get_gaussian_components(self, source): |
||
856 | for name in source.data["Components"].split(", "): |
||
857 | row_index = int(name.split()[-1]) - 1 |
||
858 | yield self.gaussian_component(row_index) |
||
859 | |||
860 | def _attach_association_info(self, source): |
||
861 | t = self.table_associations |
||
862 | mask = source.data["Source_Name"] == t["Source_Name"] |
||
863 | source.associations = t[mask] |
||
864 | |||
865 | def _attach_identification_info(self, source): |
||
866 | t = self._table_identifications |
||
867 | idx = np.nonzero(source.name == t["Source_Name"])[0][0] |
||
868 | source.identification_info = table_row_to_dict(t[idx]) |
||
869 | |||
870 | def gaussian_component(self, row_idx): |
||
871 | """Gaussian component (`SourceCatalogObjectHGPSComponent`).""" |
||
872 | data = table_row_to_dict(self.table_components[row_idx]) |
||
873 | data[SourceCatalogObject._row_index_key] = row_idx |
||
874 | return SourceCatalogObjectHGPSComponent(data=data) |
||
875 | |||
876 | def to_models(self, which="best", components_status="independent"): |
||
877 | """Create Models object from catalogue |
||
878 | |||
879 | Parameters |
||
880 | ---------- |
||
881 | which : {'best', 'pl', 'ecpl'} |
||
882 | Which spectral model |
||
883 | |||
884 | components_status : {'independent', 'linked', 'merged'} |
||
885 | Relation between the sources components: |
||
886 | 'independent' : each sub-component of a source is given as |
||
887 | a different `SkyModel` (Default) |
||
888 | 'linked' : each sub-component of a source is given as |
||
889 | a different `SkyModel` but the spectral parameters |
||
890 | except the mormalisation are linked. |
||
891 | 'merged' : the sub-components are merged into a single `SkyModel` |
||
892 | given as a `~gammapy.modeling.models.TemplateSpatialModel` |
||
893 | with a `~gammapy.modeling.models.PowerLawNormSpectralModel`. |
||
894 | In that case the relavtie weights between the components |
||
895 | cannot be adjusted. |
||
896 | |||
897 | Returns |
||
898 | ------- |
||
899 | models : `~gammapy.modeling.models.Models` |
||
900 | Models of the catalog. |
||
901 | """ |
||
902 | |||
903 | models = [] |
||
904 | for source in self: |
||
905 | if components_status == "merged": |
||
906 | m = [source.sky_model(which=which)] |
||
907 | else: |
||
908 | m = source.components_models( |
||
909 | which=which, linked=components_status == "linked" |
||
910 | ) |
||
911 | models.extend(m) |
||
912 | return Models(models) |
||
913 | |||
914 | |||
915 | class SourceCatalogLargeScaleHGPS: |
||
916 | """Gaussian band model. |
||
917 | |||
918 | This 2-dimensional model is Gaussian in ``y`` for a given ``x``, |
||
919 | and the Gaussian parameters can vary in ``x``. |
||
920 | |||
921 | One application of this model is the diffuse emission along the |
||
922 | Galactic plane, i.e. ``x = GLON`` and ``y = GLAT``. |
||
923 | |||
924 | Parameters |
||
925 | ---------- |
||
926 | table : `~astropy.table.Table` |
||
927 | Table of Gaussian parameters. |
||
928 | ``x``, ``amplitude``, ``mean``, ``stddev``. |
||
929 | interp_kwargs : dict |
||
930 | Keyword arguments passed to `ScaledRegularGridInterpolator` |
||
931 | """ |
||
932 | |||
933 | def __init__(self, table, interp_kwargs=None): |
||
934 | interp_kwargs = interp_kwargs or {} |
||
935 | interp_kwargs.setdefault("values_scale", "lin") |
||
936 | |||
937 | self.table = table |
||
938 | glon = Angle(self.table["GLON"]).wrap_at("180d") |
||
939 | |||
940 | interps = {} |
||
941 | |||
942 | for column in table.colnames: |
||
943 | values = self.table[column].quantity |
||
944 | interp = ScaledRegularGridInterpolator((glon,), values, **interp_kwargs) |
||
945 | interps[column] = interp |
||
946 | |||
947 | self._interp = interps |
||
948 | |||
949 | def _interpolate_parameter(self, parname, glon): |
||
950 | glon = glon.wrap_at("180d") |
||
951 | return self._interp[parname]((np.asanyarray(glon),), clip=False) |
||
952 | |||
953 | def peak_brightness(self, glon): |
||
954 | """Peak brightness at a given longitude. |
||
955 | |||
956 | Parameters |
||
957 | ---------- |
||
958 | glon : `~astropy.coordinates.Angle` |
||
959 | Galactic Longitude. |
||
960 | """ |
||
961 | return self._interpolate_parameter("Surface_Brightness", glon) |
||
962 | |||
963 | def peak_brightness_error(self, glon): |
||
964 | """Peak brightness error at a given longitude. |
||
965 | |||
966 | Parameters |
||
967 | ---------- |
||
968 | glon : `~astropy.coordinates.Angle` |
||
969 | Galactic Longitude. |
||
970 | """ |
||
971 | return self._interpolate_parameter("Surface_Brightness_Err", glon) |
||
972 | |||
973 | def width(self, glon): |
||
974 | """Width at a given longitude. |
||
975 | |||
976 | Parameters |
||
977 | ---------- |
||
978 | glon : `~astropy.coordinates.Angle` |
||
979 | Galactic Longitude. |
||
980 | """ |
||
981 | return self._interpolate_parameter("Width", glon) |
||
982 | |||
983 | def width_error(self, glon): |
||
984 | """Width error at a given longitude. |
||
985 | |||
986 | Parameters |
||
987 | ---------- |
||
988 | glon : `~astropy.coordinates.Angle` |
||
989 | Galactic Longitude. |
||
990 | """ |
||
991 | return self._interpolate_parameter("Width_Err", glon) |
||
992 | |||
993 | def peak_latitude(self, glon): |
||
994 | """Peak position at a given longitude. |
||
995 | |||
996 | Parameters |
||
997 | ---------- |
||
998 | glon : `~astropy.coordinates.Angle` |
||
999 | Galactic Longitude. |
||
1000 | """ |
||
1001 | return self._interpolate_parameter("GLAT", glon) |
||
1002 | |||
1003 | def peak_latitude_error(self, glon): |
||
1004 | """Peak position error at a given longitude. |
||
1005 | |||
1006 | Parameters |
||
1007 | ---------- |
||
1008 | glon : `~astropy.coordinates.Angle` |
||
1009 | Galactic Longitude. |
||
1010 | """ |
||
1011 | return self._interpolate_parameter("GLAT_Err", glon) |
||
1012 | |||
1013 | def evaluate(self, position): |
||
1014 | """Evaluate model at a given position. |
||
1015 | |||
1016 | Parameters |
||
1017 | ---------- |
||
1018 | position : `~astropy.coordinates.SkyCoord` |
||
1019 | Position on the sky. |
||
1020 | """ |
||
1021 | glon, glat = position.galactic.l, position.galactic.b |
||
1022 | width = self.width(glon) |
||
1023 | amplitude = self.peak_brightness(glon) |
||
1024 | mean = self.peak_latitude(glon) |
||
1025 | return Gaussian1D.evaluate(glat, amplitude=amplitude, mean=mean, stddev=width) |
||
1026 |