Commit b722b4775e153bd74a1e558dd10ccccff29c15fe

Authored by Jürgen Knödlseder
Committed by Luigi Tibaldo
1 parent a7dc2afa

Use gammalib optimizer in show_significance_distribution.py script

cscripts/Makefile.am
... ... @@ -232,7 +232,8 @@ testdata = $(top_srcdir)/test/data/crab.xml \
232 232 $(top_srcdir)/test/data/onoff_rmf.fits.gz \
233 233 $(top_srcdir)/test/data/onoff_rmf1.fits.gz \
234 234 $(top_srcdir)/test/data/onoff_rmf2.fits.gz \
235   - $(top_srcdir)/test/data/model_crab_radialacceptance.xml
  235 + $(top_srcdir)/test/data/model_crab_radialacceptance.xml \
  236 + $(top_srcdir)/test/data/sigdist_map.fits.gz
236 237  
237 238 # Test data to be distributed in $(pyexecdir)/cscripts/tests/data/iactdata
238 239 testiactdata = $(top_srcdir)/test/data/iactdata/hdu-index.fits.gz \
... ...
examples/show_significance_distribution.py
... ... @@ -2,7 +2,7 @@
2 2 # ==========================================================================
3 3 # Shows the distribution of significances in a given significance map.
4 4 #
5   -# Copyright (C) 2011-2017 Andreas Specovius
  5 +# Copyright (C) 2018 Andreas Specovius
6 6 #
7 7 # This program is free software: you can redistribute it and/or modify
8 8 # it under the terms of the GNU General Public License as published by
... ... @@ -17,8 +17,35 @@
17 17 # You should have received a copy of the GNU General Public License
18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19 #
  20 +# --------------------------------------------------------------------------
  21 +# What do I need for plotting:
  22 +#
  23 +# - significance map itself [mappath]
  24 +# Provided as a file path guiding to a GSkyMap in FITS format
  25 +# No default
  26 +#
  27 +# - included regions (for shrinking data space i.e. for cutting certain edges
  28 +# or at a certain radius) [includedreg]
  29 +# Provided as a file path to a FITS WCS map or DS9 regions file
  30 +# Default should be None, which means all is allowed
  31 +#
  32 +# - excluded regions [excludedreg]
  33 +# Provided as a file path to a FITS WCS map or DS9 regions file
  34 +# Default should be None, which means there are no excluded regions. In this
  35 +# case no bkg will be plotted and fitted
  36 +#
  37 +# - binning parameters for the histogram: [nbins], [sign_min], [sign_max]
  38 +# Provided as floats
  39 +# Default should be -5 to 8 with 130 bins (131 edges)
  40 +#
  41 +# - title of the histogram [title]
  42 +# Provided as string
  43 +# Default should be an empty string
  44 +#
  45 +# - Path to plotted file [plotfile]
20 46 # ==========================================================================
21 47 import sys
  48 +import math
22 49 import gammalib
23 50 import cscripts
24 51 try:
... ... @@ -35,56 +62,99 @@ except ImportError:
35 62 print('This script needs the "numpy" module')
36 63 sys.exit()
37 64  
38   -try:
39   - import scipy.optimize as optimize
40   -except ImportError:
41   - print('This script needs the "scipy" module')
42   - sys.exit()
43 65  
44   -
45   -#
46   -#
47   -#
48   -# What do I need for plotting:
49   -#
50   -#- significance map itself [mappath]
51   -# Provided as a file path guiding to a GSkyMap in FITS format
52   -# No default
53   -#
54   -#- included regions (for shrinking data space i.e. for cutting certain edges or at a certain radius) [includedreg]
55   -# Provided as a file path to a FITS WCS map or DS9 regions file
56   -# Default should be None, which means all is allowed
57   -#
58   -#- excluded regions [excludedreg]
59   -# Provided as a file path to a FITS WCS map or DS9 regions file
60   -# Default should be None, which means there are no excluded regions. In this case no bkg will be plotted and fitted
61   -#
62   -#- binning parameters for the histogram: [nbins], [sign_min], [sign_max]
63   -# Provided as floats
64   -# Default should be -5 to 8 with 130 bins (131 edges)
65   -#
66   -#- title of the histogram [title]
67   -# Provided as string
68   -# Default should be an empty string
69   -#
70   -#- Path to plotted file [plotfile]
71   -#
72   -#
  66 +# ======================= #
  67 +# Gaussian function class #
  68 +# ======================= #
  69 +class gaussian(gammalib.GPythonOptimizerFunction):
  70 +
  71 + # Constructor
  72 + def __init__(self, x_vals, y_vals):
  73 +
  74 + # Call base class constructor
  75 + gammalib.GPythonOptimizerFunction.__init__(self)
  76 +
  77 + # Set eval method
  78 + self._set_eval(self.eval)
  79 +
  80 + # Set data
  81 + self._x_vals = x_vals
  82 + self._y_vals = y_vals
  83 +
  84 + # Methods
  85 + def eval(self):
  86 + """
  87 + Evaluate function
  88 + """
  89 + # Recover parameters
  90 + pars = self._pars()
  91 + norm = pars[0].value()
  92 + mean = pars[1].value()
  93 + sigma = pars[2].value()
  94 +
  95 + # Evaluate function values
  96 + y = [norm * math.exp(-0.5*(x-mean)**2/(sigma**2)) for x in self._x_vals]
  97 +
  98 + # Compute weights (1/sqrt(y))
  99 + weight = [1.0/val if val > 0.0 else 0.0 for val in self._y_vals]
  100 +
  101 + # Compute Chi Square
  102 + value = 0.0
  103 + for i in range(len(self._x_vals)):
  104 + arg = self._y_vals[i] - y[i]
  105 + value += arg * arg * weight[i]
  106 +
  107 + # Evaluate gradient and curvature
  108 + sigma2 = sigma * sigma
  109 + sigma3 = sigma2 * sigma
  110 + for i in range(len(self._x_vals)):
  111 +
  112 + # Evaluate function gradients
  113 + dx = self._x_vals[i] - mean
  114 + dnorm = y[i] / norm * pars[0].scale()
  115 + dmean = y[i] * dx / sigma2 * pars[1].scale()
  116 + dsigma = y[i] * dx**2 / sigma3 * pars[2].scale()
  117 +
  118 + # Setup gradient vector
  119 + arg = (self._y_vals[i] - y[i]) * weight[i]
  120 + self.gradient()[0] -= arg * dnorm
  121 + self.gradient()[1] -= arg * dmean
  122 + self.gradient()[2] -= arg * dsigma
  123 +
  124 + # Setup curvature matrix
  125 + self.curvature()[0,0] += dnorm * dnorm * weight[i]
  126 + self.curvature()[0,1] += dnorm * dmean * weight[i]
  127 + self.curvature()[0,2] += dnorm * dsigma * weight[i]
  128 + self.curvature()[1,0] += dmean * dnorm * weight[i]
  129 + self.curvature()[1,1] += dmean * dmean * weight[i]
  130 + self.curvature()[1,2] += dmean * dsigma * weight[i]
  131 + self.curvature()[2,0] += dsigma * dnorm * weight[i]
  132 + self.curvature()[2,1] += dsigma * dmean * weight[i]
  133 + self.curvature()[2,2] += dsigma * dsigma * weight[i]
  134 +
  135 + # Set value
  136 + self._set_value(value)
  137 +
  138 + # Return
  139 + return
73 140  
74 141  
75 142 # ================ #
76 143 # Helper functions #
77 144 # ================ #
78   -def gaussian(x, norm, xmean, sigma):
79   - """
80   - Compute function values for gaussian function.
81   - """
82   - return norm*np.exp(-(x-xmean)**2/float(2*sigma**2))
83   -
84   -
85 145 def skymap_to_numpy_ndarray(skymap):
86 146 """
87 147 Return numpy.ndarray of pixel values of a skymap.
  148 +
  149 + Parameters
  150 + ----------
  151 + skymap : `~gammalib.GSkyMap`
  152 + Sky map.
  153 +
  154 + Returns
  155 + -------
  156 + array : `~numpy.array`
  157 + Numpy array
88 158 """
89 159 # Get map size
90 160 npix = skymap.npix()
... ... @@ -108,14 +178,19 @@ def read_regions(fpath, init_map):
108 178 ----------
109 179 fpath : str
110 180 Path to regions file
111   - init_map : GSkyMap
  181 + init_map : `~gammalib.GSkyMap`
112 182 Sky map representing fov of interest. Used for initialisation of
113 183 regions map.
  184 +
  185 + Returns
  186 + -------
  187 + map : `~gammalib.GSkyMap`
  188 + Sky map.
114 189 """
115 190  
116 191 # Create starter map for user fov filled with zeros
117   - regions_map = init_map.copy()
118   - regions_map *= 0
  192 + regions_map = init_map.copy()
  193 + regions_map *= 0.0
119 194  
120 195 # Take care about ds9 region files
121 196 if fpath.lower().endswith('.reg'):
... ... @@ -132,8 +207,8 @@ def read_regions(fpath, init_map):
132 207 # Add ds9 region map to global map
133 208 regions_map += ds9_map.map()
134 209  
135   - # Take care about fits wcs files
136   - elif fpath.lower().endswith('.fits'):
  210 + # Take care about fits WCS files
  211 + elif gammalib.GFilename(fpath).is_fits():
137 212  
138 213 # Read wcs regions map
139 214 wcs_regions = gammalib.GSkyMap(fpath)
... ... @@ -142,18 +217,47 @@ def read_regions(fpath, init_map):
142 217 regions_map += wcs_regions
143 218  
144 219 else:
145   - raise RuntimeError("Invalid regions file detected. Please provide " +
146   - "a valid ds9 or FITS WCS regions file.")
  220 + raise RuntimeError('Invalid regions file detected. Please provide ' +
  221 + 'a valid ds9 or FITS WCS regions file.')
147 222  
148 223 # Return
149 224 return regions_map
150 225  
151 226  
  227 +def linspace(minval, maxval, nbins):
  228 + """
  229 + Return a linearly spaced array
  230 +
  231 + Parameters
  232 + ----------
  233 + minval : float
  234 + Minimum value
  235 + maxval : float
  236 + Maximum value
  237 + nbins : int
  238 + Number of bins
  239 +
  240 + Returns
  241 + -------
  242 + bins : list of float
  243 + Linearly spaced array
  244 + """
  245 + # Compute bin width
  246 + binwidth = (maxval - minval) / float(nbins-1)
  247 +
  248 + # Compute bins
  249 + bins = [minval + binwidth*i for i in range(nbins)]
  250 +
  251 + # Return bins
  252 + return bins
  253 +
  254 +
152 255 # ============================== #
153 256 # Plot significance distribution #
154 257 # ============================== #
155 258 def plot_significance_distribution(mappath, nbins, sigma_min, sigma_max,
156   - includedreg, excludedreg, title):
  259 + includedreg, excludedreg, title,
  260 + plotfile):
157 261 """
158 262 Plot the significance distribution and return instance of pyplot
159 263 figure and axis.
... ... @@ -174,57 +278,29 @@ def plot_significance_distribution(mappath, nbins, sigma_min, sigma_max,
174 278 Path to global excluded region file
175 279 title : str
176 280 Title of the plot
177   -
178   - Figure and axis objects are returned.
  281 + plotfile : str
  282 + Name of file for plotting
179 283 """
180 284  
181 285 # Read significance map
182   - # =====================
183   -
184   - # Read map
185 286 significance_map = gammalib.GSkyMap(mappath+'[SIGNIFICANCE]')
186 287  
187 288  
188 289 # Read included regions
189   - # =====================
190   -
191   - # Set availability to false
192 290 inclusion_available = False
193   -
194   - # Read in case provided
195 291 if len(includedreg) > 0:
196   - # Read regions map
197   - regions_map = read_regions(includedreg, init_map=significance_map)
198   -
199   - # Make numpy boolean mask
200   - mask_include = skymap_to_numpy_ndarray(regions_map).astype(bool)
201   -
202   - # Enable masked map
  292 + regions_map = read_regions(includedreg, init_map=significance_map)
  293 + mask_include = skymap_to_numpy_ndarray(regions_map).astype(bool)
203 294 inclusion_available = True
204 295  
205   -
206 296 # Read excluded regions
207   - # =====================
208   -
209   - # Set availability to false
210 297 src_exclusion_available = False
211   -
212   - # Read in case provided
213 298 if len(excludedreg) > 0:
214   - # Read regions file
215   - regions_map = read_regions(excludedreg, init_map=significance_map)
216   -
217   - # Make numpy boolean mask
218   - mask_exclude = skymap_to_numpy_ndarray(regions_map).astype(bool)
219   -
220   - # Enable masked map
  299 + regions_map = read_regions(excludedreg, init_map=significance_map)
  300 + mask_exclude = skymap_to_numpy_ndarray(regions_map).astype(bool)
221 301 src_exclusion_available = True
222 302  
223   -
224   - # Convet maps to flat arrays and apply spatial cuts
225   - # =================================================
226   -
227   - # significance data to flat array
  303 + # Convert significance map to flat array
228 304 data = skymap_to_numpy_ndarray(significance_map)
229 305  
230 306 # Initialise dummy mask allowing all pixels
... ... @@ -241,70 +317,93 @@ def plot_significance_distribution(mappath, nbins, sigma_min, sigma_max,
241 317 mask &= ~mask_exclude
242 318 data_without_sources = data_without_sources[mask]
243 319  
244   -
245   - # Compute bin edges to user parameters
246   - # ====================================
247   -
248 320 # Compute bin edges, hence use nbins+1
249   - bin_edges = np.linspace(sigma_min, sigma_max, nbins+1)
250   -
251   -
252   - # Begin with plotting
253   - # ===================
  321 + bin_edges = linspace(sigma_min, sigma_max, nbins+1)
254 322  
255 323 # Create figure
256 324 fig = plt.figure()
257 325 ax = fig.gca()
258 326  
259   - # Draw significance histogram for full fov
260   - y, _, _ = ax.hist(data, bins=bin_edges, histtype='step', color='k', label="significance")
  327 + # Draw significance histogram for full FOV
  328 + y, _, _ = ax.hist(data, bins=bin_edges, histtype='step', color='k',
  329 + label='significance')
261 330  
262 331 # Draw the default normal distribution. Scale to fit maximum of histogram
263   - ax.plot(bin_edges, gaussian(bin_edges, y.max(),0,1), 'b-')
  332 + yvals = [y.max() * math.exp(-0.5*(x-0.0)**2/(1.0**2)) for x in bin_edges]
  333 + ax.plot(bin_edges, yvals, 'b-')
264 334 msg = 'mean: $0$\nwidth: $1$'
265   - ax.text(0.98, 0.80, msg, ha='right', va='top', bbox=dict(edgecolor='blue', facecolor='white'), transform=ax.transAxes)
  335 + ax.text(0.98, 0.80, msg, ha='right', va='top',
  336 + bbox=dict(edgecolor='blue', facecolor='white'),
  337 + transform=ax.transAxes)
266 338  
267   - # If exclusion was provided also handle significanes with excluded src data
  339 + # If exclusion was provided then also plot significances with excluded
  340 + # source data
268 341 if src_exclusion_available:
269 342  
270   - # Draw histogram of significances for data without excluded sources
  343 + # Draw histogram of significances for data with excluded sources
271 344 y, _, _ = ax.hist(data_without_sources, bins=bin_edges, histtype='step',
272   - color='k', alpha=0.5, label="significance without exclusions")
273   -
274   - # Fit a normal distribution to the binned masked significance data
275   - pars = [1.0, 0.0, 1.0]
276   - bin_centers = 0.5*(bin_edges[:-1] + bin_edges[1:])
277   -
278   - pars_opt, pars_cov = optimize.curve_fit(gaussian, xdata=bin_centers,
279   - ydata=y, p0=pars)
280   -
281   - # Compute parameters fit errors
282   - pars_err = np.sqrt(np.diag(pars_cov))
  345 + color='k', alpha=0.5,
  346 + label='significance without exclusions')
  347 +
  348 + # Set initial Gaussian parameters
  349 + par1 = gammalib.GOptimizerPar('Norm', y.max())
  350 + par2 = gammalib.GOptimizerPar('Mean', 0.0)
  351 + par3 = gammalib.GOptimizerPar('Sigma', 1.0)
  352 + pars = gammalib.GOptimizerPars()
  353 + pars.append(par1)
  354 + pars.append(par2)
  355 + pars.append(par3)
  356 +
  357 + # Set fit function
  358 + x = [0.5*(bin_edges[i]+bin_edges[i+1]) for i in range(nbins)]
  359 + fct = gaussian(x, y)
  360 +
  361 + # Optimize function and compute errors
  362 + opt = gammalib.GOptimizerLM()
  363 + opt.optimize(fct, pars)
  364 + opt.errors(fct, pars)
  365 +
  366 + # Recover parameters and errors
  367 + norm = pars[0].value()
  368 + e_norm = pars[0].error()
  369 + mean = pars[1].value()
  370 + e_mean = pars[1].error()
  371 + sigma = pars[2].value()
  372 + e_sigma = pars[2].error()
283 373  
284 374 # Log fit result
285   - print('Fit of normal distribution to excluded source significance data ' +\
  375 + print('Fit of normal distribution to excluded source significance data '+
286 376 'results in: norm=%f+=%f xmean=%f+-%f sigma=%f+-%f' %
287   - (pars_opt[0], pars_err[0], pars_opt[1], pars_err[1], pars_opt[2], pars_err[2])
288   - )
  377 + (norm, e_norm, mean, e_mean, sigma, e_sigma))
289 378  
290 379 # Draw text box
291   - msg = 'mean: $%.3f\pm%.3f$\nwidth: $%.3f\pm%.3f$' % (pars_opt[1], pars_err[1], pars_opt[2], pars_err[2])
292   - ax.text(0.98, 0.95, msg, ha='right', va='top', bbox=dict(edgecolor='red', facecolor='white'), transform=ax.transAxes)
  380 + msg = 'mean: $%.3f\pm%.3f$\nwidth: $%.3f\pm%.3f$' % \
  381 + (mean, e_mean, sigma, e_sigma)
  382 + ax.text(0.98, 0.95, msg, ha='right', va='top',
  383 + bbox=dict(edgecolor='red', facecolor='white'),
  384 + transform=ax.transAxes)
293 385  
294   - # Plot the normal (2)
295   - ax.plot(bin_edges, gaussian(bin_edges, *(pars_opt)), 'r-')
  386 + # Plot the normal
  387 + yvals = [norm * math.exp(-0.5*(x-mean)**2/(sigma**2)) for x in bin_edges]
  388 + ax.plot(bin_edges, yvals, 'r-')
296 389  
297 390 # Configure the plot
298 391 ax.set_ylim(0.5, ax.get_ylim()[1]*2.0)
299 392 ax.set_xlim(sigma_min, sigma_max)
300 393 ax.set_title(title)
301   - ax.set_xlabel("Significance")
302   - ax.set_ylabel("Entries")
  394 + ax.set_xlabel('Significance')
  395 + ax.set_ylabel('Entries')
303 396 ax.grid()
304 397 ax.set_yscale('log')
305 398  
  399 + # Show or save the plot
  400 + if len(plotfile) > 0:
  401 + plt.savefig(plotfile)
  402 + else:
  403 + plt.show()
  404 +
306 405 # Return
307   - return fig, ax
  406 + return
308 407  
309 408  
310 409 # ============================== #
... ... @@ -346,14 +445,9 @@ def show_significance_distribution():
346 445 plotfile = options[6]['value']
347 446  
348 447 # Plot significance distribution histogram
349   - fig, ax = plot_significance_distribution(args[0], nbins, sigma_min, sigma_max,
350   - includedreg, excludedreg, title)
351   -
352   - # Show or save the plot
353   - if len(plotfile) > 0:
354   - plt.savefig(plotfile)
355   - else:
356   - plt.show()
  448 + plot_significance_distribution(args[0], nbins, sigma_min, sigma_max,
  449 + includedreg, excludedreg, title,
  450 + plotfile)
357 451  
358 452 # Return
359 453 return
... ...
test/data/sigdist_map.fits.gz 0 → 100644
No preview for this file type
test/test_examples.py.in
... ... @@ -123,6 +123,8 @@ class Test(gammalib.GPythonTestSuite):
123 123 'Test show_obs.py')
124 124 self.append(self.test_show_pointings,
125 125 'Test show_pointings.py')
  126 + self.append(self.test_show_significance_distribution,
  127 + 'Test show_significance_distribution.py')
126 128  
127 129 # Return
128 130 return
... ... @@ -526,6 +528,22 @@ class Test(gammalib.GPythonTestSuite):
526 528 # Return
527 529 return
528 530  
  531 + # Test show_significance_distribution
  532 + def test_show_significance_distribution(self):
  533 + """
  534 + Test show_significance_distribution
  535 + """
  536 + # Set arguments
  537 + args = '-p example_show_significance_distribution.eps '+ \
  538 + '%s/sigdist_map.fits --exclude %s/sigdist_map.fits[EXCLUSION]' % \
  539 + (self._datadir, self._datadir)
  540 +
  541 + # Execute script
  542 + self._execute_python('show_significance_distribution', args=args)
  543 +
  544 + # Return
  545 + return
  546 +
529 547  
530 548 # ======================== #
531 549 # Main routine entry point #
... ...