1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
import sys, gammalib, math, numpy
|
9
|
from scipy import ndimage
|
10
|
from astropy.io import fits
|
11
|
|
12
|
def denoise_tv_chambolle_2d(im, weight=50, eps=2.e-4, n_iter_max=200):
|
13
|
"""Perform total-variation denoising on 2D images.
|
14
|
Parameters
|
15
|
----------
|
16
|
im : ndarray
|
17
|
Input data to be denoised.
|
18
|
weight : float, optional
|
19
|
Denoising weight. The greater `weight`, the more denoising (at
|
20
|
the expense of fidelity to `input`)
|
21
|
eps : float, optional
|
22
|
Relative difference of the value of the cost function that determines
|
23
|
the stop criterion. The algorithm stops when:
|
24
|
(E_(n-1) - E_n) < eps * E_0
|
25
|
n_iter_max : int, optional
|
26
|
Maximal number of iterations used for the optimization.
|
27
|
Returns
|
28
|
-------
|
29
|
out : ndarray
|
30
|
Denoised array of floats.
|
31
|
Notes
|
32
|
-----
|
33
|
|
34
|
***Function taken from the python module scikit-image***
|
35
|
***https://github.com/scikit-image/scikit-image/blob/master/skimage/restoration/_denoise.py***
|
36
|
|
37
|
The principle of total variation denoising is explained in
|
38
|
http://en.wikipedia.org/wiki/Total_variation_denoising.
|
39
|
This code is an implementation of the algorithm of Rudin, Fatemi and Osher
|
40
|
that was proposed by Chambolle in [1]_.
|
41
|
References
|
42
|
----------
|
43
|
.. [1] A. Chambolle, An algorithm for total variation minimization and
|
44
|
applications, Journal of Mathematical Imaging and Vision,
|
45
|
Springer, 2004, 20, 89-97.
|
46
|
"""
|
47
|
|
48
|
px = numpy.zeros_like(im)
|
49
|
py = numpy.zeros_like(im)
|
50
|
gx = numpy.zeros_like(im)
|
51
|
gy = numpy.zeros_like(im)
|
52
|
d = numpy.zeros_like(im)
|
53
|
i = 0
|
54
|
while i < n_iter_max:
|
55
|
d = -px - py
|
56
|
d[1:] += px[:-1]
|
57
|
d[:, 1:] += py[:, :-1]
|
58
|
|
59
|
out = im + d
|
60
|
E = (d ** 2).sum()
|
61
|
gx[:-1] = numpy.diff(out, axis=0)
|
62
|
gy[:, :-1] = numpy.diff(out, axis=1)
|
63
|
norm = numpy.sqrt(gx ** 2 + gy ** 2)
|
64
|
E += weight * norm.sum()
|
65
|
norm *= 0.5 / weight
|
66
|
norm += 1
|
67
|
px -= 0.25 * gx
|
68
|
px /= norm
|
69
|
py -= 0.25 * gy
|
70
|
py /= norm
|
71
|
E /= float(im.size)
|
72
|
if i == 0:
|
73
|
E_init = E
|
74
|
E_previous = E
|
75
|
else:
|
76
|
if numpy.abs(E_previous - E) < eps * E_init:
|
77
|
break
|
78
|
else:
|
79
|
E_previous = E
|
80
|
i += 1
|
81
|
return out
|
82
|
|
83
|
|
84
|
def background2fits( data, fname ) :
|
85
|
"""Function just for taking final background data, and writing it to a fits file.
|
86
|
Does no binning or smearing.
|
87
|
|
88
|
**Args:**
|
89
|
data : dictionary containing info about background, see write_background_to_fits for more info.
|
90
|
fname : output fits filename to write background to
|
91
|
"""
|
92
|
|
93
|
ndetx = len( data['DETX_LO'] )
|
94
|
ndety = len( data['DETY_LO'] )
|
95
|
nenez = len( data['BGD' ] )
|
96
|
formx = '%dE' % ndetx
|
97
|
formz = '%dE' % nenez
|
98
|
formxyz = '%dE' % ( ndetx * ndety * nenez )
|
99
|
bgddim = '(%d,%d,%d)' % ( ndetx, ndety, nenez )
|
100
|
|
101
|
|
102
|
|
103
|
cols = []
|
104
|
for key in [ 'DETX_LO', 'DETX_HI', 'DETY_LO', 'DETY_HI' ] :
|
105
|
|
106
|
cols.append( fits.Column( name=key , format=formx , unit='deg' , array=[ data[key] ] ) )
|
107
|
for key in [ 'ENERG_LO', 'ENERG_HI' ] :
|
108
|
|
109
|
encol = [ en.TeV() for en in data[key] ]
|
110
|
cols.append( fits.Column( name=key , format=formz , unit='TeV' , array=[ encol ] ) )
|
111
|
for key in [ 'BGD' ] :
|
112
|
|
113
|
cols.append( fits.Column( name=key , format=formxyz, unit='1/s/MeV/sr', array=[ data[key] ], dim=bgddim ) )
|
114
|
|
115
|
|
116
|
tablehdu = fits.BinTableHDU.from_columns( cols )
|
117
|
|
118
|
|
119
|
tablehdu.name = 'BACKGROUND'
|
120
|
tablehdu.header.set( 'TDIM7', bgddim )
|
121
|
|
122
|
|
123
|
print 'writing fits table to %s' % fname
|
124
|
tablehdu.writeto( fname, clobber=True)
|
125
|
|
126
|
def write_background_to_fits( obs, fname, binwidth=0.4, nbufferbin=5, nenergybins=5, energyprange=[0.02,0.99], zeroval=1e-5, smoothmode='gauss', smooth=0.4 ) :
|
127
|
"""Create and write a background from a GObservations object to a fits file.
|
128
|
|
129
|
**Arguments:**
|
130
|
obs : GObservations object
|
131
|
fname : str, filename of output fits file
|
132
|
|
133
|
**Options:**
|
134
|
background binning options:
|
135
|
binwidth : float, width in degrees of one side of a bin
|
136
|
nbufferbin : int, number of extra (empty) buffer bins to add to the
|
137
|
edge of the camera before smoothing. If 0, will only
|
138
|
create bins out to event with largest camera offset.
|
139
|
nenergybins : int, number of equal-statistics energy bins to create
|
140
|
energyprange : list of 2 floats, [min,max]. Only use the events whose
|
141
|
energy is between the min and max percentiles. [0.0,1.0]
|
142
|
for all events.
|
143
|
zeroval : float, after binning events, replace all 0-event bins
|
144
|
with this value
|
145
|
smoothmode : str, type of smoothing to apply to background
|
146
|
'gauss' : smooth each bin with a 'smooth'-degree-wide gaussian
|
147
|
'totalvariation' : apply total-variation denoising, with
|
148
|
weight 'smooth'. See denoise_tv_chambolle_2d() for
|
149
|
more information.
|
150
|
'none' for no smoothing
|
151
|
|
152
|
smooth : float, if smoothmode is 'gauss', then smooth determines
|
153
|
the gaussian width [deg] of the smoothing. if smoothmode is
|
154
|
'totalvariation', smooth is the weight given to the function
|
155
|
denoise_tv_chambolle_2d(..., weight=smooth)
|
156
|
|
157
|
**Returns:**
|
158
|
dictionary with info about background produced, keys are:
|
159
|
['enbins' ] : list of pairs of energies, one pair for each energy bin
|
160
|
['detx' ] : list of detx pairs of angles for camera bin edges
|
161
|
['dety' ] : list of detx pairs of angles for camera bin edges
|
162
|
['bgd' ] : background counts 3D array, [ienergy][idetx][idety]
|
163
|
['bgd_smooth'] : same as 'bgd', except smoothed
|
164
|
['bgd_norm' ] : same as 'bgd_smooth', except normalized for exposure
|
165
|
|
166
|
"""
|
167
|
|
168
|
print
|
169
|
print 'Using %s smoothing!' % smoothmode
|
170
|
print
|
171
|
|
172
|
quart_min = min( energyprange )
|
173
|
quart_max = max( energyprange )
|
174
|
|
175
|
|
176
|
nev = 0
|
177
|
e_min, e_max = 0.0, 0.0
|
178
|
maxoffset = 0.0
|
179
|
for run in obs :
|
180
|
for ev in run.events() :
|
181
|
en = ev.energy()
|
182
|
dx = ev.dir().detx() * gammalib.rad2deg
|
183
|
dy = ev.dir().dety() * gammalib.rad2deg
|
184
|
offset = math.sqrt( dx*dx + dy*dy )
|
185
|
if offset > maxoffset :
|
186
|
maxoffset = offset
|
187
|
if nev == 0 :
|
188
|
e_min = en
|
189
|
e_max = en
|
190
|
if en > e_max : e_max = en
|
191
|
if en < e_min : e_min = en
|
192
|
nev += 1
|
193
|
binr = maxoffset + ( nbufferbin * binwidth )
|
194
|
|
195
|
|
196
|
|
197
|
e_max = e_max + gammalib.GEnergy(1,'GeV')
|
198
|
print 'total: %d events from %.3f to %.1f TeV' % ( nev, e_min.TeV(), e_max.TeV() )
|
199
|
|
200
|
|
201
|
nfinebins = 1000
|
202
|
uselogbinning = True
|
203
|
enfinespec = gammalib.GEbounds( nfinebins, e_min, e_max, uselogbinning )
|
204
|
enfinebins = numpy.zeros( nfinebins )
|
205
|
enfinebinwidth = (e_max.log10TeV() - e_min.log10TeV() ) / nfinebins
|
206
|
|
207
|
|
208
|
for run in obs :
|
209
|
for ev in run.events() :
|
210
|
enfinebins[ enfinespec.index( ev.energy() ) ] += 1
|
211
|
|
212
|
|
213
|
|
214
|
|
215
|
|
216
|
|
217
|
def frac2en( fraclist ) :
|
218
|
"""search binned histogram enfinespec (defined a few lines up) for the
|
219
|
energies that have the given fraction of events at or below that energy.
|
220
|
|
221
|
**Example:**
|
222
|
frac2en( [0.4 ] ) # find what energy has 40% of events below that energy
|
223
|
frac2en( [0.88] ) # find what energy has 88% of events below that energy
|
224
|
|
225
|
**Args:**
|
226
|
fraclist : float or list of floats, each float from 0.0 to 1.0
|
227
|
|
228
|
**Returns:**
|
229
|
list of gammalib.GEnergy objects, each element corrosponding
|
230
|
to each fraction in 'fraclist'
|
231
|
"""
|
232
|
|
233
|
if type(fraclist) is not type( [] ) :
|
234
|
fraclist = [ fraclist ]
|
235
|
|
236
|
|
237
|
enlist = [ 0 for i in range(len(fraclist)) ]
|
238
|
last_nfrac = 0.0
|
239
|
|
240
|
|
241
|
for i in range(enfinespec.size()) :
|
242
|
nfrac = numpy.sum(enfinebins[:i]) / float(nev)
|
243
|
|
244
|
|
245
|
for i_frac, frac in enumerate( fraclist ) :
|
246
|
|
247
|
|
248
|
if last_nfrac < frac and nfrac > frac :
|
249
|
enlist[i_frac] = enfinespec.elogmean(i)
|
250
|
last_nfrac = nfrac
|
251
|
|
252
|
return enlist
|
253
|
|
254
|
|
255
|
quart_min_en, quart_median_en, quart_max_en = frac2en( [ quart_min, 0.5, quart_max ] )
|
256
|
|
257
|
|
258
|
quartbins = []
|
259
|
quart_width = ( quart_max - quart_min ) / nenergybins
|
260
|
for i in range(nenergybins) :
|
261
|
quartbins.append( [quart_min + (quart_width*i), quart_min + (quart_width*(i+1))] )
|
262
|
newquartedges = sorted(list(set([ j for i in quartbins for j in i ])))[1:-1]
|
263
|
|
264
|
|
265
|
newquartedges_en = frac2en( newquartedges )
|
266
|
|
267
|
|
268
|
detbins = []
|
269
|
j = 0
|
270
|
while True :
|
271
|
inner = j * binwidth
|
272
|
outer = (j+1) * binwidth
|
273
|
if inner > binr :
|
274
|
break
|
275
|
detbins.append( [ i for i in [inner,outer] ] )
|
276
|
detbins.insert( 0, [ -1*i for i in [outer,inner] ] )
|
277
|
j += 1
|
278
|
|
279
|
|
280
|
print 'energy bins, ~%d events (%d%% of all events) per bin' % ( nev*(quartbins[0][1]-quartbins[0][0]), 100*(quartbins[0][1]-quartbins[0][0] ) )
|
281
|
background = {}
|
282
|
background['DETX_LO' ] = background['DETY_LO'] = [ det[0] for det in detbins ]
|
283
|
background['DETX_HI' ] = background['DETY_HI'] = [ det[1] for det in detbins ]
|
284
|
background['ENERG_LO'] = []
|
285
|
background['ENERG_HI'] = []
|
286
|
background['BGD' ] = []
|
287
|
|
288
|
|
289
|
enbins = []
|
290
|
for fracrange in quartbins :
|
291
|
energybin = {}
|
292
|
emin, emax = frac2en( fracrange )
|
293
|
enbins.append( [emin, emax] )
|
294
|
print '%5.3f - %5.3f TeV' % ( emin.TeV(), emax.TeV() )
|
295
|
background['ENERG_LO'].append(emin)
|
296
|
background['ENERG_HI'].append(emax)
|
297
|
logmean = pow(10, ( emin.log10TeV() + emax.log10TeV() ) / 2.0 )
|
298
|
|
299
|
|
300
|
ndet = len(detbins)
|
301
|
nen = len( enbins)
|
302
|
bgd = numpy.zeros( (len(enbins),ndet,ndet), dtype='int32')
|
303
|
|
304
|
|
305
|
mindet = min( background['DETX_LO'] )
|
306
|
def det2ind(val) :
|
307
|
return int( ( val - mindet ) / binwidth )
|
308
|
def en2ind(en) :
|
309
|
for i, erange in enumerate( enbins ) :
|
310
|
if en > erange[0] and en < erange[1] :
|
311
|
return i
|
312
|
livetime = 0
|
313
|
for run in obs :
|
314
|
livetime += run.livetime()
|
315
|
for ev in run.events() :
|
316
|
idetx = det2ind( ev.dir().detx() * gammalib.rad2deg )
|
317
|
idety = det2ind( ev.dir().dety() * gammalib.rad2deg )
|
318
|
ien = en2ind( ev.energy() )
|
319
|
if type(ien) is type(3) :
|
320
|
bgd[ien][idety][idetx] += 1
|
321
|
|
322
|
|
323
|
|
324
|
bgd = bgd.astype(numpy.float32)
|
325
|
|
326
|
|
327
|
bgd[ bgd == 0 ] = zeroval
|
328
|
|
329
|
|
330
|
bgd_smooth = []
|
331
|
for i in range(nen) :
|
332
|
|
333
|
if smoothmode == 'gauss' :
|
334
|
|
335
|
|
336
|
bgd_smooth.append( ndimage.gaussian_filter( bgd[i], smooth/binwidth ) )
|
337
|
|
338
|
elif smoothmode == 'totalvariation' :
|
339
|
presmoothsum = numpy.sum( bgd[i] )
|
340
|
bgd_smooth.append( denoise_tv_chambolle_2d( bgd[i], weight=smooth ) )
|
341
|
postsmoothsum = numpy.sum( bgd_smooth[-1] )
|
342
|
|
343
|
|
344
|
bgd_smooth[-1] *= presmoothsum
|
345
|
bgd_smooth[-1] /= postsmoothsum
|
346
|
|
347
|
elif smoothmode == 'none' :
|
348
|
bgd_smooth.append( bgd[i] )
|
349
|
|
350
|
else :
|
351
|
raise ValueError( "smoothmode is currently %s, must be one of ['gauss', 'totalvariation', 'none']" % repr(smoothmode) )
|
352
|
|
353
|
|
354
|
zmin = numpy.min( [bgd,bgd_smooth] )
|
355
|
zmax = numpy.max( [bgd,bgd_smooth] )
|
356
|
|
357
|
|
358
|
bin_sr = binwidth * binwidth * gammalib.deg2rad * gammalib.deg2rad
|
359
|
bgd_norm = []
|
360
|
for i in range(nen) :
|
361
|
emin, emax = enbins[i]
|
362
|
en_norm = emax.MeV() - emin.MeV()
|
363
|
normbin = bin_sr * livetime * en_norm
|
364
|
bgd_norm.append( bgd_smooth[i] / normbin )
|
365
|
norm_min = numpy.min( bgd_norm )
|
366
|
norm_max = numpy.max( bgd_norm )
|
367
|
|
368
|
|
369
|
mevnorm = [ en[1].MeV() - en[0].MeV() for en in enbins ]
|
370
|
|
371
|
|
372
|
background['BGD'] = bgd_norm
|
373
|
|
374
|
|
375
|
|
376
|
background2fits( background, fname )
|
377
|
|
378
|
ret = {}
|
379
|
ret['maxoffset' ] = maxoffset
|
380
|
ret['enbins' ] = enbins
|
381
|
ret['detx' ] = detbins
|
382
|
ret['dety' ] = detbins
|
383
|
ret['bgd' ] = bgd
|
384
|
ret['bgd_smooth'] = bgd_smooth
|
385
|
ret['bgd_norm' ] = bgd_norm
|
386
|
return ret
|
387
|
|
388
|
if __name__ == '__main__' :
|
389
|
|
390
|
if len(sys.argv) == 1 :
|
391
|
print 'Script for converting a GObservations xml file into a GCTABackground3D fits file.'
|
392
|
print 'Usage is:'
|
393
|
print ' $ %s xmlfile.xml outputbackground.fits' % __file__
|
394
|
sys.exit(0)
|
395
|
|
396
|
if len(sys.argv) != 3 :
|
397
|
print 'Incorrect number of arguments.'
|
398
|
print
|
399
|
print 'Usage is:'
|
400
|
print ' $ %s xmlfile.xml outputbackground.fits' % __file__
|
401
|
sys.exit(1)
|
402
|
|
403
|
xmlfile = sys.argv[1]
|
404
|
fitsfile = sys.argv[2]
|
405
|
obs = gammalib.GObservations()
|
406
|
obs.load( xmlfile )
|
407
|
|
408
|
write_background_to_fits( obs, fitsfile )
|
409
|
|