forked from erccarls/GammaLike_dev
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Tools.py
441 lines (360 loc) · 18.6 KB
/
Tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
#--------------------------------------------------------------------------
# Tools.py
# This module contains helper functions for likelihood analysis including
# PSF convolutions and pixel transformations to and from healpix space.
# Author: Eric Carlson (erccarls@ucsc.edu) 11/20/2014
#--------------------------------------------------------------------------
import numpy as np
import pyfits
import healpy
from scipy.integrate import quad
from scipy.special import expn
from scipy.interpolate import RegularGridInterpolator
def hpix2ang(hpix, nside=256):
"""
Transform the healpix index into lat/lon
:param hpix: healpix index
:param nside: healpix nside parameter
:returns l,b: latitude and longitude of the input.
"""
b, l = np.rad2deg(healpy.pix2ang(nside, hpix))
b = 90-b
return l, b
def ang2hpix(l, b, nside=256):
"""
Transform lat/lon into healpix index.
:param l: longitudes
:param b: latitudes.
:param nside: healpix nside parameter.
:return hpix index: the healpix index corresponining to the input angular coordinates.
"""
l, b = np.deg2rad(l), np.deg2rad(-b+90)
l = np.array(l)
if l.ndim == 0:
if l > 180:
l -= 360
else:
idx = np.where(l > 180)[0]
l[idx] -= 360
return healpy.ang2pix(nside, b, l, nest=False)
def GetSpec(specType):
"""
Given a 2FGL Spectral type return lambdas for the spectrum and integrated spectrum
:param specType: Can be 'PowerLaw','PLExpCutoff', or 'LogParabola'
:returns Spec,IntegratedSpec: the spectrum and integrated spectrum. See function def for param ordering.
"""
if specType == 'PowerLaw':
Spec = lambda e, gamma: e**-gamma
IntegratedSpec = lambda e1, e2, gamma: (e1*e2)**-gamma * (e1*e2**gamma - e1**gamma*e2)/(gamma-1)
elif specType == 'PLExpCutoff':
Spec = lambda e, gamma, cutoff: e**-gamma * np.exp(-e/cutoff)
IntegratedSpec = lambda e1, e2, gamma, cutoff: (e1**(1-gamma)*expn(gamma, e1/cutoff)
-e2**(1-gamma)*expn(gamma, e2/cutoff))
elif specType == 'LogParabola':
Spec = lambda e, alpha, beta, pivot: (e/pivot)**-(alpha+beta*np.log(e/pivot))
IntegratedSpec = lambda e1, e2, alpha, beta, pivot: quad(Spec, e1, e2, args=(alpha, beta, pivot))[0]
else:
raise Exception("Spectral type not supported.")
return Spec, IntegratedSpec
#--------------------------------------------------------------------------
# Here we generate models and convolve them with
# instrumental response functions.
#--------------------------------------------------------------------------
def GetPSF(E_min, E_max, psfFile='/data/fermi_data_1-8-14/psf_P7REP_SOURCE_BOTH.fits'):
"""
Spectrally weight the PSF(E) and return the average. The spectrum is taken to be the global average of the P7
galdiffuse model. In practice, the energy bins should be narrow enough that this is sufficient.
:param E_min: Minimum energy to integrate
:param E_max: Maximum energy to integrate
:param psfFile: absolute path to an output file from gtpsf.
:returns thetas, avgPSF: thetas is the angular distance in degrees. avgPSF is the spectrally averaged point-spread
and has the same units as gtpsf output.
"""
hdu = pyfits.open(psfFile)
thetas = np.array([theta[0] for theta in hdu[2].data])
energies = np.array([energy[0] for energy in hdu[1].data])
PSFs = np.array([psf[2] for psf in hdu[1].data])
E_min_bin = np.argmin(np.abs(energies-E_min))
E_max_bin = np.argmin(np.abs(energies-E_max))+1
if E_max_bin > len(PSFs): E_min_bin, E_max_bin = len(PSFs)-2, len(PSFs)-1
weights = energies[E_min_bin:E_max_bin]**-GetSpectralIndex(E_min, E_max)
return thetas, np.average(PSFs[E_min_bin:E_max_bin], weights=weights, axis=0)
def ApplyPSF(hpix, E_min, E_max, PSFFile='P7Clean_Front+Back.fits', sigma=.1, smoothed=False):
"""
WARNING INCOMPLETE: Normalization off and smoothing below healpix scale explodes.
This method takes a healpix input 'hpix', and a spectrally averaged energy 'E'. It then looks up the corresponding
PSF from tables -> calculates the legendre transform coefficients -> transforms the input pixles into spherical
harmonics -> re-weight the alm coefficients according to the PSF -> returns the inverse transform
**WARNING: DO NOT USE. THIS METHOD IS NOT YET COMPLETED**
"""
# Spherical Harmonic transform
alm = healpy.sphtfunc.map2alm(hpix)
# get maximum value of l
l_max = healpy.sphtfunc.Alm.getlmax(len(alm))
x = np.linspace(-30, 30, 10000) # Cos(0) to Cos(pi)
psf_y = (sigma*np.sqrt(2*np.pi))*np.exp(-x*x/(2*sigma**2))
psf_x = np.cos(np.deg2rad(x))
# Fit the PSF to l_max legendre polynomials
cls = np.sqrt(4*np.pi/(2*np.arange(0, l_max+1)+1))*np.polynomial.legendre.legfit(psf_x, psf_y, l_max)
conv_alm=healpy.sphtfunc.almxfl(alm, cls)
# Find nside and return inverse transformed map.
nside = healpy.get_nside(hpix)
if smoothed is False:
return healpy.sphtfunc.alm2map(conv_alm, nside=nside, verbose=False)
else:
return healpy.sphtfunc.alm2map(alm, nside=nside, sigma=np.deg2rad(sigma), verbose=False)
def ApplyGaussianPSF(hpix, E_min, E_max, psfFile, multiplier=1.):
"""
Finds the spectral weighted average PSF, determines the FWHM and blurs by Gaussian kernel of that size.
:param hpix: input healpix map
:param E_min: Minimum energy for spectral weighting
:param E_max: Max energy for spectral weighting
:param psfFile: Output of gtpsf
:param multiplier: Sigma = multiplier*FWHM from fermi gtpsf.
:return hpix array: returns the input hpix with PSF convolved.
"""
theta, psf = GetPSF(E_min, E_max, psfFile)
# Find FWHM
halfmax = np.argmin(np.abs(0.5-psf/psf.max()))
FWHM = np.deg2rad(2*theta[halfmax])
# Spherical Harmonic transform
alm = healpy.sphtfunc.map2alm(hpix)
# inverse transform with gaussian beam
return healpy.sphtfunc.alm2map(alm, nside=healpy.get_nside(hpix), fwhm=FWHM, verbose=False)
currentExpCube, expcubehdu = None, None # keeps track of the current gtexpcube2
def GetExpMap(E_min, E_max, l, b, expcube):
"""
Returns the effective area given the energy range and angular coordinates.
:param E_min: Min energy in MeV
:param E_max: Max Energy in MeV
:param l: Galactic longitude.
:param b: Galactic latitude.
:param expcube: Exposure cube file over observation from Fermitools gtexpcube2.
:return Effective Exposure: value of the effective exposure in cm^2*s at the given coordinates.
"""
# check if the expCube has already been opened.
global currentExpCube, expcubehdu
if expcube != currentExpCube:
expcubehdu = pyfits.open(expcube)
# Find the average photon energy over the band
alpha = GetSpectralIndex(E_min, E_max)
if E_min == E_max:
average_E = E_min
else:
average_E = 10**(0.5*(np.log10(E_min)+np.log10(E_max)))
# average_E = (1-alpha)/(alpha-2)*(E_min**(2-alpha)-E_max**(2-alpha))/(E_min**(1-alpha)-E_max**(1-alpha))
# Find the energy bin in the expcube file
Ebin = int(np.round((np.log(average_E)-expcubehdu[0].header['CRVAL3'])/expcubehdu[0].header['CDELT3']))
if Ebin >= expcubehdu[0].header['NAXIS3']:
Ebin = expcubehdu[0].header['NAXIS3']-1
# convert 0-360 to -180-180
l,b = np.array(l), np.array(b)
if l.ndim == 0:
if l > 180:
l -= 360
else:
idx = np.where(l > 180)[0]
l[idx] -= 360.
# Find lat/lon bin on expmap
l_bin = np.round((l-expcubehdu[0].header['CRVAL1'])/expcubehdu[0].header['CDELT1']
+ expcubehdu[0].header['CRPIX1']).astype(np.int32)
b_bin = np.round((b-expcubehdu[0].header['CRVAL2'])/expcubehdu[0].header['CDELT2']
+ expcubehdu[0].header['CRPIX2']).astype(np.int32)
# Check for points on the border
if np.ndim(l_bin) > 0:
idx_l = np.where(l_bin == expcubehdu[0].header['NAXIS1'])[0]
idx_b = np.where(b_bin == expcubehdu[0].header['NAXIS2'])[0]
l_bin[idx_l] = l_bin[idx_l]-1
b_bin[idx_b] = b_bin[idx_b]-1
else:
if l_bin == expcubehdu[0].header['NAXIS1']:
l_bin -= 1
if b_bin == expcubehdu[0].header['NAXIS2']:
b_bin -= 1
# Return the exposure map in cm*s
return expcubehdu[0].data[Ebin, b_bin, l_bin]
def GetSpectralIndex(E_min, E_max):
"""
Returns the spectal index between evaluated at the two endpoints E_min and E_max based on the
averaged P7REPv15 diffuse model
:param E_min: Min energy in MeV
:param E_max: Max energy in MeV
:return spectral index: The power law index averaged over the given energy (positive value).
"""
E = np.array([58.473133087158203, 79.970359802246108, 109.37088726489363, 149.58030713742139, 204.57243095354417,
279.7820134691566, 382.64185792772452, 523.31738421248383, 715.71125569520109, 978.83734991844688,
1338.6998597146596, 1830.8632323330951, 2503.9669281982829, 3424.532355440329, 4683.5370393234753,
6405.4057377695362, 8760.3070758200847, 11980.97094929486, 16385.688725918291, 22409.769304923335,
30648.559770668966, 41916.282280063475, 57326.501908367274, 78402.17791960774, 107226.17459483664,
146647.10628359963, 200560.85990769751, 274295.61718814232, 375138.42752394517, 513055.37160154793])
dnde = np.array([1.3259335, 0.94195729, 0.66580701, 0.46162829, 0.30296713, 0.18484889, 0.10698333, 0.059697378,
0.032260861, 0.01673951, 0.0082548652, 0.0039907703, 0.0018546022, 0.00082937587, 0.0003599966,
0.00015557533, 6.7215013e-05, 2.8863404e-05, 1.2341489e-05, 5.3399754e-06, 2.2966778e-06,
9.9477847e-07, 4.53333e-07, 2.1135656e-07, 9.9832157e-08, 4.6697188e-08, 2.1986754e-08,
1.0368451e-08, 5.0197251e-09, 2.4097735e-09])
# Find bin and check bounds
E_bin_min, E_bin_max = int(np.argmin(np.abs(E-E_min))), int(np.argmin(np.abs(E-E_max)))
if E_bin_min == E_bin_max:
E_bin_max = E_bin_min+1
if E_bin_max >= len(dnde):
E_bin_min, E_bin_max = len(dnde)-3, len(dnde)-1
return -np.log(dnde[E_bin_min]/dnde[E_bin_max])/np.log(E[E_bin_min]/E[E_bin_max])
def CartesianCountMap2Healpix(cartCube, nside):
"""
This is a static function which takes an input cartesian datacube and returns a 2d healpix array.
It simply maps each cartesian pixel to the healpix grid (individually for each spectral bin).
Primarily intended for converting gtsrcmaps output into healpix format.
params:
:param cartCube: Fits filename containing the source cartesian countmap
:param nside: healpix nside
:return hpixcube: First index corresponds to the energies in cartCube, second dimension is the healpix grid.
"""
# open the fits
hdu = pyfits.open(cartCube)
# initialize the target array
hpix = np.zeros(shape=(hdu[0].data.shape[0], 12*nside**2))
def Getlatlon(i, j):
l = (i-hdu[0].header['CRPIX1']+1)*hdu[0].header['CDELT1']+hdu[0].header['CRVAL1']
b = (j-hdu[0].header['CRPIX2']+1)*hdu[0].header['CDELT2']+hdu[0].header['CRVAL2']
return l, b
# iterate over latitudes
i_list = np.arange(hdu[0].data.shape[2]) # list of longitude bins
for j in range(hdu[0].data.shape[1]):
l, b = Getlatlon(i_list, j)
# convert l,b to healpix index
hpixIndex = ang2hpix(l, b, nside)
# iterate over energy bins, summing the contibution from each pixel.
for i_E in range(hpix.shape[0]):
hpix[i_E, hpixIndex] += hdu[0].data[i_E, j]
return hpix
def SampleCartesianMap(fits, E_min, E_max, nside, E_bins=5):
"""
Given a cartesian fits mapcube and energy range, returns a spectrally weighted average diffuse model
in units of (s cm^2)^-1. Just need to multiply by effective area and PSF in order to obtain count map.
:param fits: fits filename. Assumed to run from -180, 180 and -90,-90 and have energy keywords like
fermi diffuse model
:param E_min: Min energy in MeV
:param E_max: Max energy in MeV
:param nside: healpix nside
:param E_bins: Number of subbins for integration.
:returns: Healpix sampling of the input cartesian data cube with units in (s cm^2)^-1
"""
hdu = pyfits.open(fits)
# TODO: Check sensitivity of fitting results to number of sub-bins (E_bins). Do we need more integration accuracy?
# Define the grid spacings
energies = np.log10([e[0] for e in hdu[1].data])
lats = np.linspace(-90, 90, hdu[0].header['NAXIS2'])
lons = np.linspace(-180, 180, hdu[0].header['NAXIS1'])
# Build the interpolator
rgi = RegularGridInterpolator((energies, lats, lons), hdu[0].data, method='linear',
bounds_error=False, fill_value=np.float32(0.))
# Init the healpix grid and compute the energy bins.
master = np.zeros(12*nside**2)
bin_edges = np.logspace(np.log10(E_min), np.log10(E_max), E_bins+1)
# Get the latitude and longitude.
l, b = hpix2ang(np.arange(12*nside**2))
idx = np.where(l > 180)[0]
l[idx] -= 360.
# Iterate over the sub-bins to return the integrated spectrum.
for i_E in range(len(bin_edges)-1):
central_energy = 10.**(0.5*(np.log10(bin_edges[i_E]) + np.log10(bin_edges[i_E+1])))
bin_width = bin_edges[i_E+1]-bin_edges[i_E]
# Units of diffuse model are (sr s cm^2 MeV)^-1
master += rgi((np.log10(central_energy), b, l))*bin_width
# Units of returned model are (s cm^2)^-1
return master*healpy.pixelfunc.nside2pixarea(nside)
def InterpolateHealpix(healpixcube, energies, E_min, E_max, E_bins=3, nside_out=None):
"""
Integrate a healpix cube energy range, returns a spectrally weighted average
in units of (s cm^2)^-1. Just need to multiply by effective area and PSF in order to obtain count map.
:param healpixcube: a healpixcube with first dimension energy and second dimension the healpix index.
:param energies: a list of energies for the healpix cube in MeV.
:param E_min: Min energy in MeV
:param E_max: Max energy in MeV
:param E_bins: Number of subbins for integration.
:param nside_out: if not None, can specify a new nside for up/downsampling.
:returns: Spectral subsampling of the input cartesian data cube with units in (s cm^2)^-1
"""
nside = np.sqrt(healpixcube.shape[1]/12) # Get nside based on shape
# Define the grid spacings
energies = np.log10(energies)
# Build the interpolator
rgi = RegularGridInterpolator((energies, np.arange(12*nside**2)), healpixcube, method='linear',
bounds_error=False, fill_value=np.float32(0.))
# Init the healpix grid and compute the energy bins.
master = np.zeros(12*nside**2)
bin_edges = np.logspace(np.log10(E_min), np.log10(E_max), E_bins+1)
# Iterate over the sub-bins to return the integrated spectrum.
for i_E in range(len(bin_edges)-1):
central_energy = 10.**(0.5*(np.log10(bin_edges[i_E]) + np.log10(bin_edges[i_E+1])))
bin_width = bin_edges[i_E+1]-bin_edges[i_E]
# Units of diffuse model are (sr s cm^2 MeV)^-1
master += rgi((np.log10(central_energy), np.arange(12*nside**2)))*bin_width
if (nside_out is None) or (nside == nside_out):
# Units of returned model are (s cm^2)^-1
return master*healpy.pixelfunc.nside2pixarea(nside)
else:
return ResizeHealpix(master*healpy.pixelfunc.nside2pixarea(nside), nside_out=nside_out, average=False)
def AsyncInterpolateHealpix(healpixcube, energies, E_min, E_max, index, comp, E_bins=3, nside_out=None,):
return index, comp, InterpolateHealpix(healpixcube, energies, E_min, E_max, E_bins=E_bins, nside_out=nside_out)
def galprop2numpy(fits):
"""
Converts the galprop healpix fits output (which is in table form) into a numpy array.
:param fits: fits filename to read in.
:returns energies, healpixcube: energies is an array with energies in MeV. healpixcube
The first dimension is energy and the second is healpix index.
"""
hdulist = pyfits.open(fits)
dat = hdulist[1].data
hdr = hdulist[1].header
energies = np.array([hdr['EMIN']*np.exp(hdr['DELTAE']*i) for i in range(len(hdulist[1].data[0]))])
healpixcube = np.zeros(shape=(len(dat[0]), len(dat)))
for i in range(len(dat[0])):
healpixcube[i] = dat.field(i)
return energies, healpixcube
def ResizeHealpix(map_in, nside_out, average=True):
"""
Change nside of a healpix array.
:param map_in: healpix array to resample.
:param nside_out: new nside parameter.
:param average: if True, the pixels are averaged for downsampling,
otherwise pixel values are divided among the subpixels (summed for upsampling)
"""
if average:
return healpy.pixelfunc.ud_grade(map_in, nside_out, power=0) # Keep density invariant
else:
return healpy.pixelfunc.ud_grade(map_in, nside_out, power=-2) # Keep sum invariant
#----------------------------------------------------------------------------
# Testing for ApplyPSF
#----------------------------------------------------------------------------
# nside = 256
# hpix_im = np.zeros(12*nside**2)
# hpix_im[ang2hpix(0,0)]=1
# # set center pixel to 1
# hpix_im[ang2hpix(0,0)]=1
# hpix_im = ApplyPSF(hpix_im,100,1000,sigma=1)
# test = healpy.cartview(hpix_im,return_projected_map=True,latra=[-5,5],lonra=[-5,5])
# plt.clf()
# im = plt.imshow(test,extent=[-5,5,-5,5])
# plt.colorbar(im)
# print 'Sum', np.sum(test)
# hpix_im = np.zeros(12*nside**2)
# hpix_im[ang2hpix(0,0)]=1
# hpix_im = ApplyPSF(hpix_im,100,1000,sigma=1,smoothed=True)
# test2 = healpy.cartview(hpix_im,return_projected_map=True,latra=[-5,5],lonra=[-5,5])
# plt.clf()
# im = plt.imshow(test2,extent=[-5,5,-5,5])
# plt.colorbar(im)
# print 'Sum', np.sum(test2)
# plt.show()
# plt.plot(np.linspace(-5,5,800),test[399,:])#/np.max(test))
# plt.plot(np.linspace(-5,5,800),test2[399,:])#/np.max(test2))
#----------------------------------------------------------------------------
# End Testing for ApplyPSF
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
# Testing for ApplyGaussianPSF
#----------------------------------------------------------------------------
# withPSF = ApplyGaussianPSF(hpix_im,E_min=100,E_max=150)
# test2 = healpy.cartview(withPSF,return_projected_map=True,latra=[-5,5],lonra=[-5,5])
# im = plt.imshow(test2,extent=[-5,5,-5,5])