|
1
|
|
|
""" |
|
2
|
|
|
Classes containing the Geometry config parameters for the high-level interface |
|
3
|
|
|
and also some functions to support creating the base Geometry of various |
|
4
|
|
|
DL4 datasets. |
|
5
|
|
|
""" |
|
6
|
|
|
|
|
7
|
|
|
import re |
|
8
|
|
|
from enum import Enum |
|
9
|
|
|
|
|
10
|
|
|
from astropy import units as u |
|
11
|
|
|
from astropy.coordinates import SkyCoord |
|
12
|
|
|
from gammapy.maps import Map, MapAxis, RegionGeom, WcsGeom |
|
13
|
|
|
from regions import CircleSkyRegion, PointSkyRegion |
|
14
|
|
|
|
|
15
|
|
|
from asgardpy.base.base import AngleType, BaseConfig, EnergyType, FrameEnum |
|
16
|
|
|
|
|
17
|
|
|
__all__ = [ |
|
18
|
|
|
"EnergyAxisConfig", |
|
19
|
|
|
"EnergyEdgesCustomConfig", |
|
20
|
|
|
"GeomConfig", |
|
21
|
|
|
"MapAxesConfig", |
|
22
|
|
|
"MapFrameShapeConfig", |
|
23
|
|
|
"ProjectionEnum", |
|
24
|
|
|
"SelectionConfig", |
|
25
|
|
|
"SkyPositionConfig", |
|
26
|
|
|
"WcsConfig", |
|
27
|
|
|
"create_counts_map", |
|
28
|
|
|
"generate_geom", |
|
29
|
|
|
"get_energy_axis", |
|
30
|
|
|
"get_source_position", |
|
31
|
|
|
] |
|
32
|
|
|
|
|
33
|
|
|
|
|
34
|
|
|
# Basic Components to define the main GeomConfig |
|
35
|
|
|
class EnergyAxisConfig(BaseConfig): |
|
36
|
|
|
""" |
|
37
|
|
|
Config section for getting main information for creating an Energy type |
|
38
|
|
|
MapAxis object. |
|
39
|
|
|
""" |
|
40
|
|
|
|
|
41
|
|
|
min: EnergyType = 1 * u.GeV |
|
42
|
|
|
max: EnergyType = 1 * u.TeV |
|
43
|
|
|
nbins: int = 5 |
|
44
|
|
|
per_decade: bool = True |
|
45
|
|
|
|
|
46
|
|
|
|
|
47
|
|
|
class EnergyEdgesCustomConfig(BaseConfig): |
|
48
|
|
|
""" |
|
49
|
|
|
Config section for getting information of energy edges for creating an |
|
50
|
|
|
Energy type MapAxis object. |
|
51
|
|
|
""" |
|
52
|
|
|
|
|
53
|
|
|
edges: list[float] = [] |
|
54
|
|
|
unit: str = "TeV" |
|
55
|
|
|
|
|
56
|
|
|
|
|
57
|
|
|
class MapAxesConfig(BaseConfig): |
|
58
|
|
|
""" |
|
59
|
|
|
Config section for getting main information for creating a MapAxis object. |
|
60
|
|
|
""" |
|
61
|
|
|
|
|
62
|
|
|
name: str = "energy" |
|
63
|
|
|
axis: EnergyAxisConfig = EnergyAxisConfig() |
|
64
|
|
|
axis_custom: EnergyEdgesCustomConfig = EnergyEdgesCustomConfig() |
|
65
|
|
|
|
|
66
|
|
|
|
|
67
|
|
|
class SelectionConfig(BaseConfig): |
|
68
|
|
|
""" |
|
69
|
|
|
Config section for extra selection criteria on creating a MapDataset object. |
|
70
|
|
|
""" |
|
71
|
|
|
|
|
72
|
|
|
offset_max: AngleType = 2.5 * u.deg |
|
73
|
|
|
|
|
74
|
|
|
|
|
75
|
|
|
class MapFrameShapeConfig(BaseConfig): |
|
76
|
|
|
""" |
|
77
|
|
|
Config section for getting frame size information for creating a Map object. |
|
78
|
|
|
""" |
|
79
|
|
|
|
|
80
|
|
|
width: AngleType = 5 * u.deg |
|
81
|
|
|
height: AngleType = 5 * u.deg |
|
82
|
|
|
|
|
83
|
|
|
|
|
84
|
|
|
class SkyPositionConfig(BaseConfig): |
|
85
|
|
|
""" |
|
86
|
|
|
Config section for getting main information for creating a SkyCoord object. |
|
87
|
|
|
""" |
|
88
|
|
|
|
|
89
|
|
|
frame: FrameEnum = FrameEnum.icrs |
|
90
|
|
|
lon: AngleType = 0 * u.deg |
|
91
|
|
|
lat: AngleType = 0 * u.deg |
|
92
|
|
|
radius: AngleType = 0 * u.deg |
|
93
|
|
|
|
|
94
|
|
|
|
|
95
|
|
|
class ProjectionEnum(str, Enum): |
|
96
|
|
|
"""Config section for list of sky projection methods.""" |
|
97
|
|
|
|
|
98
|
|
|
tan = "TAN" |
|
99
|
|
|
car = "CAR" |
|
100
|
|
|
|
|
101
|
|
|
|
|
102
|
|
|
class WcsConfig(BaseConfig): |
|
103
|
|
|
""" |
|
104
|
|
|
Config section for getting main sky projection information for creating a |
|
105
|
|
|
base Geometry. |
|
106
|
|
|
""" |
|
107
|
|
|
|
|
108
|
|
|
skydir: SkyPositionConfig = SkyPositionConfig() |
|
109
|
|
|
binsize: AngleType = 0.1 * u.deg |
|
110
|
|
|
proj: ProjectionEnum = ProjectionEnum.tan |
|
111
|
|
|
map_frame_shape: MapFrameShapeConfig = MapFrameShapeConfig() |
|
112
|
|
|
binsize_irf: AngleType = 0.2 * u.deg |
|
113
|
|
|
|
|
114
|
|
|
|
|
115
|
|
|
class GeomConfig(BaseConfig): |
|
116
|
|
|
""" |
|
117
|
|
|
Config section for getting main information for creating a base Geometry. |
|
118
|
|
|
""" |
|
119
|
|
|
|
|
120
|
|
|
wcs: WcsConfig = WcsConfig() |
|
121
|
|
|
selection: SelectionConfig = SelectionConfig() |
|
122
|
|
|
axes: list[MapAxesConfig] = [MapAxesConfig()] |
|
123
|
|
|
from_events_file: bool = True |
|
124
|
|
|
reco_psf: bool = False |
|
125
|
|
|
|
|
126
|
|
|
|
|
127
|
|
|
def get_energy_axis(axes_config, only_edges=False, custom_range=False): |
|
128
|
|
|
""" |
|
129
|
|
|
Read from a MapAxesConfig section to return the required energy axis/range. |
|
130
|
|
|
|
|
131
|
|
|
Parameters |
|
132
|
|
|
---------- |
|
133
|
|
|
axes_config: `asgardpy.base.geom.MapAxesConfig` |
|
134
|
|
|
COnfig for generating an energy axis |
|
135
|
|
|
only_edges: bool |
|
136
|
|
|
If True, only return an array of energy edges |
|
137
|
|
|
custom_range: bool |
|
138
|
|
|
If True, use the given list of energy edges and energy unit. |
|
139
|
|
|
|
|
140
|
|
|
Return |
|
141
|
|
|
------ |
|
142
|
|
|
energy_range: `gammapy.maps.MapAxis` |
|
143
|
|
|
Energy Axis formed from the given config parameters |
|
144
|
|
|
""" |
|
145
|
|
|
energy_axis = axes_config.axis |
|
146
|
|
|
energy_axis_custom = axes_config.axis_custom |
|
147
|
|
|
|
|
148
|
|
|
if custom_range: |
|
149
|
|
|
energy_range = energy_axis_custom.edges * u.Unit(energy_axis_custom.unit) |
|
150
|
|
|
else: |
|
151
|
|
|
energy_range = MapAxis.from_energy_bounds( |
|
152
|
|
|
energy_min=u.Quantity(energy_axis.min), |
|
153
|
|
|
energy_max=u.Quantity(energy_axis.max), |
|
154
|
|
|
nbin=int(energy_axis.nbins), |
|
155
|
|
|
per_decade=energy_axis.per_decade, |
|
156
|
|
|
name=axes_config.name, |
|
157
|
|
|
) |
|
158
|
|
|
if only_edges: |
|
159
|
|
|
energy_range = energy_range.edges |
|
160
|
|
|
|
|
161
|
|
|
return energy_range |
|
162
|
|
|
|
|
163
|
|
|
|
|
164
|
|
|
def get_source_position(target_region, fits_header=None): |
|
165
|
|
|
""" |
|
166
|
|
|
Function to fetch the target source position and the angular radius for |
|
167
|
|
|
the generating the Counts Map or ON region. |
|
168
|
|
|
|
|
169
|
|
|
Parameters |
|
170
|
|
|
---------- |
|
171
|
|
|
target_region: `asgardpy.data.geom.SkyPositionConfig` |
|
172
|
|
|
Config containing the information on the target source position |
|
173
|
|
|
fits_header: `astropy.io.fits.Header` |
|
174
|
|
|
FITS Header information of the events fits file, only for Fermi-LAT |
|
175
|
|
|
DL3 files. If None is passed, the information is collected from the |
|
176
|
|
|
config_target. |
|
177
|
|
|
|
|
178
|
|
|
Return |
|
179
|
|
|
------ |
|
180
|
|
|
center_pos: dict |
|
181
|
|
|
Dict information on the central `astropy.coordinates.SkyCoord` position |
|
182
|
|
|
and `astropy.units.Quantity` angular radius. |
|
183
|
|
|
""" |
|
184
|
|
|
if fits_header: |
|
185
|
|
|
try: |
|
186
|
|
|
dsval2 = fits_header["DSVAL2"] |
|
187
|
|
|
list_str_check = re.findall(r"[-+]?\d*\.\d+|\d+", dsval2) |
|
188
|
|
|
except KeyError: |
|
189
|
|
|
history = str(fits_header["HISTORY"]) |
|
190
|
|
|
str_ = history.split("angsep(RA,DEC,")[1] |
|
191
|
|
|
list_str_check = re.findall(r"[-+]?\d*\.\d+|\d+", str_)[:3] |
|
192
|
|
|
ra_pos, dec_pos, events_radius = (float(k) for k in list_str_check) |
|
193
|
|
|
|
|
194
|
|
|
source_pos = SkyCoord(ra_pos, dec_pos, unit="deg", frame="fk5") |
|
195
|
|
|
else: |
|
196
|
|
|
source_pos = SkyCoord( |
|
197
|
|
|
u.Quantity(target_region.lon), |
|
198
|
|
|
u.Quantity(target_region.lat), |
|
199
|
|
|
frame=target_region.frame, |
|
200
|
|
|
) |
|
201
|
|
|
events_radius = target_region.radius |
|
202
|
|
|
|
|
203
|
|
|
center_pos = {"center": source_pos, "radius": events_radius} |
|
204
|
|
|
|
|
205
|
|
|
return center_pos |
|
206
|
|
|
|
|
207
|
|
|
|
|
208
|
|
|
def create_counts_map(geom_config, center_pos): |
|
209
|
|
|
""" |
|
210
|
|
|
Generate the counts Map object using the information provided in the |
|
211
|
|
|
geom section of the Config and the dict information on the position and |
|
212
|
|
|
Counts Map size of the target source. Used currently only for Fermi-LAT |
|
213
|
|
|
DL3 files. |
|
214
|
|
|
|
|
215
|
|
|
Parameters |
|
216
|
|
|
---------- |
|
217
|
|
|
geom_config: `asgardpy.base.geom.GeomConfig` |
|
218
|
|
|
Config information on creating the Base Geometry of the DL4 dataset. |
|
219
|
|
|
center_pos: dict |
|
220
|
|
|
Dict information on the central `astropy.coordinates.SkyCoord` position |
|
221
|
|
|
and `astropy.units.Quantity` angular radius. |
|
222
|
|
|
|
|
223
|
|
|
Return |
|
224
|
|
|
------ |
|
225
|
|
|
counts_map: `gammapy.maps.Map` |
|
226
|
|
|
Map object of the Counts information. |
|
227
|
|
|
""" |
|
228
|
|
|
energy_axes = geom_config.axes[0] |
|
229
|
|
|
energy_axis = get_energy_axis(energy_axes) |
|
230
|
|
|
bin_size = geom_config.wcs.binsize.to_value(u.deg) |
|
231
|
|
|
|
|
232
|
|
|
# For fetching information from the events fits file to resize the Map size. |
|
233
|
|
|
if geom_config.from_events_file: |
|
234
|
|
|
counts_map = Map.create( |
|
235
|
|
|
skydir=center_pos["center"].galactic, |
|
236
|
|
|
binsz=bin_size, |
|
237
|
|
|
npix=( |
|
238
|
|
|
int(center_pos["radius"] * 2 / bin_size), |
|
239
|
|
|
int(center_pos["radius"] * 2 / bin_size), |
|
240
|
|
|
), # Using the limits from the events fits file |
|
241
|
|
|
proj=geom_config.wcs.proj, |
|
242
|
|
|
frame="galactic", |
|
243
|
|
|
axes=[energy_axis], |
|
244
|
|
|
dtype=float, |
|
245
|
|
|
) |
|
246
|
|
|
else: |
|
247
|
|
|
# Using the config information |
|
248
|
|
|
width_ = geom_config.wcs.map_frame_shape.width.to_value(u.deg) |
|
249
|
|
|
width_in_pixel = int(width_ / bin_size) |
|
250
|
|
|
height_ = geom_config.wcs.map_frame_shape.height.to_value(u.deg) |
|
251
|
|
|
height_in_pixel = int(height_ / bin_size) |
|
252
|
|
|
|
|
253
|
|
|
counts_map = Map.create( |
|
254
|
|
|
skydir=center_pos["center"].galactic, |
|
255
|
|
|
binsz=bin_size, |
|
256
|
|
|
npix=(width_in_pixel, height_in_pixel), |
|
257
|
|
|
proj=geom_config.wcs.proj, |
|
258
|
|
|
frame="galactic", |
|
259
|
|
|
axes=[energy_axis], |
|
260
|
|
|
dtype=float, |
|
261
|
|
|
) |
|
262
|
|
|
|
|
263
|
|
|
return counts_map |
|
264
|
|
|
|
|
265
|
|
|
|
|
266
|
|
|
def create_1d_std_geom(geom_config, center_pos, energy_axis): |
|
267
|
|
|
""" |
|
268
|
|
|
Distinct set of steps to generate Gammapy RegionGeom object for the case of |
|
269
|
|
|
the standard 1D dataset. |
|
270
|
|
|
""" |
|
271
|
|
|
# Defining the ON region's geometry for DL4 dataset |
|
272
|
|
|
if center_pos["radius"] == 0 * u.deg: |
|
273
|
|
|
on_region = PointSkyRegion(center_pos["center"]) |
|
274
|
|
|
# Hack to allow for the joint fit |
|
275
|
|
|
# (otherwise pointskyregion.contains returns None) |
|
276
|
|
|
on_region.meta = {"include": False} |
|
277
|
|
|
else: |
|
278
|
|
|
on_region = CircleSkyRegion( |
|
279
|
|
|
center=center_pos["center"], |
|
280
|
|
|
radius=u.Quantity(center_pos["radius"]), |
|
281
|
|
|
) |
|
282
|
|
|
return RegionGeom.create(region=on_region, axes=[energy_axis]) |
|
283
|
|
|
|
|
284
|
|
|
|
|
285
|
|
|
def create_non_1d_std_geom(tag, geom_config, energy_axis, center_pos): |
|
286
|
|
|
""" |
|
287
|
|
|
Distinct set of steps to generate Gammapy WcsGeom object for the case other |
|
288
|
|
|
than the standard 1D dataset. |
|
289
|
|
|
""" |
|
290
|
|
|
width_ = geom_config.wcs.map_frame_shape.width.to_value("deg") |
|
291
|
|
|
height_ = geom_config.wcs.map_frame_shape.height.to_value("deg") |
|
292
|
|
|
|
|
293
|
|
|
geom_params = {} |
|
294
|
|
|
|
|
295
|
|
|
if "ex" in tag: # For exclusion regions - include for 1D data as well |
|
296
|
|
|
bin_size = geom_config.wcs.binsize.to_value("deg") |
|
297
|
|
|
width_ = int(width_ / bin_size) |
|
298
|
|
|
height_ = int(height_ / bin_size) |
|
299
|
|
|
|
|
300
|
|
|
if "1d" in tag: |
|
301
|
|
|
geom_params["npix"] = (width_, height_) |
|
302
|
|
|
else: |
|
303
|
|
|
# 3D-ex |
|
304
|
|
|
geom_params["width"] = (width_, height_) |
|
305
|
|
|
else: |
|
306
|
|
|
# 3D dataset for DL4 creation |
|
307
|
|
|
geom_params["width"] = (width_, height_) |
|
308
|
|
|
geom_params["axes"] = [energy_axis] |
|
309
|
|
|
|
|
310
|
|
|
geom_params["skydir"] = center_pos["center"] |
|
311
|
|
|
geom_params["frame"] = center_pos["center"].frame |
|
312
|
|
|
geom_params["binsz"] = geom_config.wcs.binsize |
|
313
|
|
|
geom_params["proj"] = geom_config.wcs.proj |
|
314
|
|
|
|
|
315
|
|
|
# Main geom for 3D Dataset |
|
316
|
|
|
return WcsGeom.create(**geom_params) |
|
317
|
|
|
|
|
318
|
|
|
|
|
319
|
|
|
def generate_geom(tag, geom_config, center_pos): |
|
320
|
|
|
""" |
|
321
|
|
|
Generate from a given target source position, the geometry of the ON |
|
322
|
|
|
events and the axes information on reco energy and true energy, |
|
323
|
|
|
for which a DL4 dataset of a given tag, can be defined. |
|
324
|
|
|
|
|
325
|
|
|
Can also generate Base geometry for the excluded regions for a 3D dataset. |
|
326
|
|
|
|
|
327
|
|
|
Parameters |
|
328
|
|
|
---------- |
|
329
|
|
|
tag: str |
|
330
|
|
|
Determining either the {1, 3}d Dataset type of "excluded", for creating |
|
331
|
|
|
base geometry for the excluded regions. |
|
332
|
|
|
geom_config: `asgardpy.base.geom.GeomConfig` |
|
333
|
|
|
Config information on creating the Base Geometry of the DL4 dataset. |
|
334
|
|
|
center_pos: dict |
|
335
|
|
|
Dict information on the central `astropy.coordinates.SkyCoord` position |
|
336
|
|
|
and `astropy.units.Quantity` angular radius. |
|
337
|
|
|
|
|
338
|
|
|
Return |
|
339
|
|
|
------ |
|
340
|
|
|
geom: 'gammapy.maps.RegionGeom' or `gammapy.maps.WcsGeom` |
|
341
|
|
|
appropriate Base geometry objects for {1, 3}D type of DL4 Datasets. |
|
342
|
|
|
""" |
|
343
|
|
|
# Getting the energy axes |
|
344
|
|
|
axes_list = geom_config.axes |
|
345
|
|
|
|
|
346
|
|
|
for axes_ in axes_list: |
|
347
|
|
|
if axes_.name == "energy": |
|
348
|
|
|
custom_range = len(axes_.axis_custom.edges) > 1 |
|
349
|
|
|
energy_axis = get_energy_axis(axes_, custom_range=custom_range) |
|
350
|
|
|
|
|
351
|
|
|
if custom_range: |
|
352
|
|
|
energy_axis = MapAxis.from_energy_edges(energy_axis, name=axes_.name) |
|
353
|
|
|
|
|
354
|
|
|
if tag == "1d": |
|
355
|
|
|
geom = create_1d_std_geom(geom_config, center_pos, energy_axis) |
|
356
|
|
|
else: |
|
357
|
|
|
geom = create_non_1d_std_geom(tag, geom_config, energy_axis, center_pos) |
|
358
|
|
|
|
|
359
|
|
|
return geom |
|
360
|
|
|
|