Commit 2de0afeaceefb29da9e9278278bf496bf361c0c0

Authored by Jürgen Knödlseder
1 parent b58507bd

Replace ASCII output files by FITS files in cssens (#1707)

ChangeLog
1 -2021-05-07 1 +2021-05-11
2 2
3 * Version 2.0.0 released 3 * Version 2.0.0 released
4 ======================== 4 ========================
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 Replace std::tmpnam() by gammalib::tmpnam() function in ctselect (#3324) 17 Replace std::tmpnam() by gammalib::tmpnam() function in ctselect (#3324)
18 Remove implicit conversion warnings (#3324) 18 Remove implicit conversion warnings (#3324)
19 Add support of rectangular On/Off regions (#3248) 19 Add support of rectangular On/Off regions (#3248)
  20 + Replace ASCII output files by FITS files (#1707)
20 21
21 22
22 2021-04-28 23 2021-04-28
1 New Features and Important Changes in ctools 2.0.0 1 New Features and Important Changes in ctools 2.0.0
2 2
3 -7 May 2021 3 +11 May 2021
4 4
5 5
6 Introduction 6 Introduction
@@ -286,6 +286,8 @@ The test source position is now used as pointing direction, eventually offset @@ -286,6 +286,8 @@ The test source position is now used as pointing direction, eventually offset
286 in galactic latitude by a number of degrees specified using the "offset" 286 in galactic latitude by a number of degrees specified using the "offset"
287 parameter (#3555). 287 parameter (#3555).
288 288
  289 +The ASCII output file was replaced by a FITS file (#1707).
  290 +
289 291
290 csspec - Generates Spectral Energy Distribution 292 csspec - Generates Spectral Energy Distribution
291 ----------------------------------------------- 293 -----------------------------------------------
@@ -358,7 +360,7 @@ regions (#3248). @@ -358,7 +360,7 @@ regions (#3248).
358 360
359 Examples 361 Examples
360 -------- 362 --------
361 -None 363 +The show_sensitivity.py was adapted to deal also with FITS output files (#1707).
362 364
363 365
364 caldb 366 caldb
README.md
1 ctools information 1 ctools information
2 ================== 2 ==================
3 -* Version: 2.0.0.dev (7 May 2021) 3 +* Version: 2.0.0.dev (11 May 2021)
4 * GammaLib dependency: 2.0.0.dev 4 * GammaLib dependency: 2.0.0.dev
5 5
6 [![Build Status](https://cta-jenkins.irap.omp.eu/buildStatus/icon?job=ctools-integrate-os)](https://cta-jenkins.irap.omp.eu/job/ctools-integrate-os/) 6 [![Build Status](https://cta-jenkins.irap.omp.eu/buildStatus/icon?job=ctools-integrate-os)](https://cta-jenkins.irap.omp.eu/job/ctools-integrate-os/)
cscripts/csphasecrv.py
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 # ========================================================================== 2 # ==========================================================================
3 # Generates spectra as a function of phase 3 # Generates spectra as a function of phase
4 # 4 #
5 -# Copyright (C) 2017-2019 Rolf Buehler 5 +# Copyright (C) 2017-2021 Rolf Buehler
6 # 6 #
7 # This program is free software: you can redistribute it and/or modify 7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by 8 # it under the terms of the GNU General Public License as published by
@@ -88,12 +88,12 @@ class csphasecrv(ctools.csobservation): @@ -88,12 +88,12 @@ class csphasecrv(ctools.csobservation):
88 state = {'base' : ctools.csobservation.__getstate__(self), 88 state = {'base' : ctools.csobservation.__getstate__(self),
89 'srcname' : self._srcname, 89 'srcname' : self._srcname,
90 'phbins' : self._phbins, 90 'phbins' : self._phbins,
91 - 'stacked' : self._stacked,  
92 'onoff' : self._onoff, 91 'onoff' : self._onoff,
  92 + 'stacked' : self._stacked,
93 'fits' : self._fits, 93 'fits' : self._fits,
  94 + 'fitmodels' : self._fitmodels,
94 'nthreads' : self._nthreads, 95 'nthreads' : self._nthreads,
95 - 'excl_reg_map' : self._excl_reg_map,  
96 - 'fitmodels' : self._fitmodels} 96 + 'excl_reg_map' : self._excl_reg_map}
97 97
98 # Return pickled dictionary 98 # Return pickled dictionary
99 return state 99 return state
@@ -110,12 +110,12 @@ class csphasecrv(ctools.csobservation): @@ -110,12 +110,12 @@ class csphasecrv(ctools.csobservation):
110 ctools.csobservation.__setstate__(self, state['base']) 110 ctools.csobservation.__setstate__(self, state['base'])
111 self._srcname = state['srcname'] 111 self._srcname = state['srcname']
112 self._phbins = state['phbins'] 112 self._phbins = state['phbins']
113 - self._stacked = state['stacked']  
114 self._onoff = state['onoff'] 113 self._onoff = state['onoff']
  114 + self._stacked = state['stacked']
115 self._fits = state['fits'] 115 self._fits = state['fits']
  116 + self._fitmodels = state['fitmodels']
116 self._nthreads = state['nthreads'] 117 self._nthreads = state['nthreads']
117 self._excl_reg_map = state['excl_reg_map'] 118 self._excl_reg_map = state['excl_reg_map']
118 - self._fitmodels = state['fitmodels']  
119 119
120 # Return 120 # Return
121 return 121 return
@@ -291,7 +291,7 @@ class csphasecrv(ctools.csobservation): @@ -291,7 +291,7 @@ class csphasecrv(ctools.csobservation):
291 'phmax': phmax, 291 'phmax': phmax,
292 'pars': pars, 292 'pars': pars,
293 'values': {}} 293 'values': {}}
294 - 294 +
295 # Store fit results 295 # Store fit results
296 phname = str(phmin)+'-'+str(phmax) 296 phname = str(phmin)+'-'+str(phmax)
297 297
@@ -310,14 +310,14 @@ class csphasecrv(ctools.csobservation): @@ -310,14 +310,14 @@ class csphasecrv(ctools.csobservation):
310 310
311 # Append result to list of dictionaries 311 # Append result to list of dictionaries
312 results.append(result) 312 results.append(result)
313 - 313 +
314 # Create FITS table from results 314 # Create FITS table from results
315 table = self._create_fits_table(results) 315 table = self._create_fits_table(results)
316 316
317 # Create FITS file and append FITS table to FITS file 317 # Create FITS file and append FITS table to FITS file
318 self._fits = gammalib.GFits() 318 self._fits = gammalib.GFits()
319 self._fits.append(table) 319 self._fits.append(table)
320 - 320 +
321 # Return 321 # Return
322 return 322 return
323 323
@@ -339,7 +339,7 @@ class csphasecrv(ctools.csobservation): @@ -339,7 +339,7 @@ class csphasecrv(ctools.csobservation):
339 339
340 # Save to fits 340 # Save to fits
341 self._fits.saveto(outfile, self._clobber()) 341 self._fits.saveto(outfile, self._clobber())
342 - 342 +
343 # Return 343 # Return
344 return 344 return
345 345
@@ -368,25 +368,33 @@ class csphasecrv(ctools.csobservation): @@ -368,25 +368,33 @@ class csphasecrv(ctools.csobservation):
368 # Log file name 368 # Log file name
369 name = 'Phase [%s] file' % phname 369 name = 'Phase [%s] file' % phname
370 self._log_value(gammalib.NORMAL, name, outfile) 370 self._log_value(gammalib.NORMAL, name, outfile)
371 - 371 +
372 # Return 372 # Return
373 return 373 return
374 374
375 - def _phase_bin(self,phbin): 375 + def _phase_bin(self, phbin):
  376 + """
  377 + Analyse one phase bin
  378 +
  379 + Parameters
  380 + ----------
  381 + phbin : list of float
  382 + Phase bin limits
  383 + """
376 384
377 # Write time bin into header 385 # Write time bin into header
378 self._log_header2(gammalib.TERSE, 'PHASE %f - %f' % 386 self._log_header2(gammalib.TERSE, 'PHASE %f - %f' %
379 (phbin[0], phbin[1])) 387 (phbin[0], phbin[1]))
380 388
381 # Select events 389 # Select events
382 - select = ctools.ctselect(self.obs().copy()) 390 + select = ctools.ctselect(self.obs())
383 select['emin'] = self['emin'].real() 391 select['emin'] = self['emin'].real()
384 select['emax'] = self['emax'].real() 392 select['emax'] = self['emax'].real()
385 select['tmin'] = 'UNDEFINED' 393 select['tmin'] = 'UNDEFINED'
386 select['tmax'] = 'UNDEFINED' 394 select['tmax'] = 'UNDEFINED'
387 - select['rad'] = 'UNDEFINED'  
388 - select['ra'] = 'UNDEFINED'  
389 - select['dec'] = 'UNDEFINED' 395 + select['rad'] = 'UNDEFINED'
  396 + select['ra'] = 'UNDEFINED'
  397 + select['dec'] = 'UNDEFINED'
390 select['expr'] = 'PHASE>' + str(phbin[0]) + ' && PHASE<' + str(phbin[1]) 398 select['expr'] = 'PHASE>' + str(phbin[0]) + ' && PHASE<' + str(phbin[1])
391 select.run() 399 select.run()
392 400
@@ -457,13 +465,13 @@ class csphasecrv(ctools.csobservation): @@ -457,13 +465,13 @@ class csphasecrv(ctools.csobservation):
457 # Switch screen logging on in debug mode 465 # Switch screen logging on in debug mode
458 if self._logDebug(): 466 if self._logDebug():
459 self._log.cout(True) 467 self._log.cout(True)
460 - 468 +
461 # Get parameters 469 # Get parameters
462 self._get_parameters() 470 self._get_parameters()
463 471
464 # Write observation into logger 472 # Write observation into logger
465 self._log_observations(gammalib.NORMAL, self.obs(), 'Observation') 473 self._log_observations(gammalib.NORMAL, self.obs(), 'Observation')
466 - 474 +
467 # Dictionary to save phase fitted models 475 # Dictionary to save phase fitted models
468 self._fitmodels = {} 476 self._fitmodels = {}
469 477
@@ -507,7 +515,7 @@ class csphasecrv(ctools.csobservation): @@ -507,7 +515,7 @@ class csphasecrv(ctools.csobservation):
507 515
508 # Save results to XML files (one per phase bin) 516 # Save results to XML files (one per phase bin)
509 self._save_xml() 517 self._save_xml()
510 - 518 +
511 # Return 519 # Return
512 return 520 return
513 521
@@ -569,7 +577,7 @@ class csphasecrv(ctools.csobservation): @@ -569,7 +577,7 @@ class csphasecrv(ctools.csobservation):
569 # Return 577 # Return
570 return self._excl_reg_map 578 return self._excl_reg_map
571 579
572 - 580 +
573 # ======================== # 581 # ======================== #
574 # Main routine entry point # 582 # Main routine entry point #
575 # ======================== # 583 # ======================== #
cscripts/cssens.par
@@ -23,7 +23,7 @@ caldb, s, a, prod2,,, &quot;Calibration database&quot; @@ -23,7 +23,7 @@ caldb, s, a, prod2,,, &quot;Calibration database&quot;
23 irf, s, a, South_50h,,, "Instrument response function" 23 irf, s, a, South_50h,,, "Instrument response function"
24 edisp, b, h, no,,, "Apply energy dispersion?" 24 edisp, b, h, no,,, "Apply energy dispersion?"
25 deadc, r, h, 0.98,0,1, "Deadtime correction factor" 25 deadc, r, h, 0.98,0,1, "Deadtime correction factor"
26 -outfile, f, h, sensitivity.dat,,, "Output sensitivity ASCII file" 26 +outfile, f, a, sensitivity.fits,,, "Output sensitivity FITS file"
27 27
28 # 28 #
29 # Script parameters 29 # Script parameters
@@ -51,5 +51,3 @@ clobber, b, h, yes,,, &quot;Overwrite existing output files with new output files?&quot; @@ -51,5 +51,3 @@ clobber, b, h, yes,,, &quot;Overwrite existing output files with new output files?&quot;
51 debug, b, h, no,,, "Activate debugging mode?" 51 debug, b, h, no,,, "Activate debugging mode?"
52 mode, s, h, ql,,, "Mode of automatic parameters" 52 mode, s, h, ql,,, "Mode of automatic parameters"
53 logfile, f, h, cssens.log,,, "Log filename" 53 logfile, f, h, cssens.log,,, "Log filename"
54 -  
55 -  
cscripts/cssens.py
@@ -54,6 +54,7 @@ class cssens(ctools.csobservation): @@ -54,6 +54,7 @@ class cssens(ctools.csobservation):
54 # Initialise class members 54 # Initialise class members
55 self._ebounds = gammalib.GEbounds() 55 self._ebounds = gammalib.GEbounds()
56 self._obs_ebounds = [] 56 self._obs_ebounds = []
  57 + self._fits = None
57 self._srcname = '' 58 self._srcname = ''
58 self._ra = None 59 self._ra = None
59 self._dec = None 60 self._dec = None
@@ -78,6 +79,7 @@ class cssens(ctools.csobservation): @@ -78,6 +79,7 @@ class cssens(ctools.csobservation):
78 state = {'base' : ctools.csobservation.__getstate__(self), 79 state = {'base' : ctools.csobservation.__getstate__(self),
79 'ebounds' : self._ebounds, 80 'ebounds' : self._ebounds,
80 'obs_ebounds' : self._obs_ebounds, 81 'obs_ebounds' : self._obs_ebounds,
  82 + 'fits' : self._fits,
81 'srcname' : self._srcname, 83 'srcname' : self._srcname,
82 'ra' : self._ra, 84 'ra' : self._ra,
83 'dec' : self._dec, 85 'dec' : self._dec,
@@ -100,6 +102,7 @@ class cssens(ctools.csobservation): @@ -100,6 +102,7 @@ class cssens(ctools.csobservation):
100 ctools.csobservation.__setstate__(self, state['base']) 102 ctools.csobservation.__setstate__(self, state['base'])
101 self._ebounds = state['ebounds'] 103 self._ebounds = state['ebounds']
102 self._obs_ebounds = state['obs_ebounds'] 104 self._obs_ebounds = state['obs_ebounds']
  105 + self._fits = state['fits']
103 self._srcname = state['srcname'] 106 self._srcname = state['srcname']
104 self._ra = state['ra'] 107 self._ra = state['ra']
105 self._dec = state['dec'] 108 self._dec = state['dec']
@@ -145,7 +148,6 @@ class cssens(ctools.csobservation): @@ -145,7 +148,6 @@ class cssens(ctools.csobservation):
145 self['sigma'].real() 148 self['sigma'].real()
146 self['max_iter'].integer() 149 self['max_iter'].integer()
147 self['type'].string() 150 self['type'].string()
148 - self['outfile'].filename()  
149 self['edisp'].boolean() 151 self['edisp'].boolean()
150 self['debug'].boolean() 152 self['debug'].boolean()
151 153
@@ -154,6 +156,10 @@ class cssens(ctools.csobservation): @@ -154,6 +156,10 @@ class cssens(ctools.csobservation):
154 gammalib.GEnergy(emin, 'TeV'), 156 gammalib.GEnergy(emin, 'TeV'),
155 gammalib.GEnergy(emax, 'TeV')) 157 gammalib.GEnergy(emax, 'TeV'))
156 158
  159 + # Read ahead output parameters
  160 + if self._read_ahead():
  161 + self['outfile'].filename()
  162 +
157 # Write input parameters into logger 163 # Write input parameters into logger
158 self._log_parameters(gammalib.TERSE) 164 self._log_parameters(gammalib.TERSE)
159 165
@@ -690,6 +696,88 @@ class cssens(ctools.csobservation): @@ -690,6 +696,88 @@ class cssens(ctools.csobservation):
690 # Return results 696 # Return results
691 return result 697 return result
692 698
  699 + def _create_fits(self, results):
  700 + """
  701 + Create FITS file from results
  702 +
  703 + Parameters
  704 + ----------
  705 + results : list of dict
  706 + List of result dictionaries
  707 + """
  708 + # Create FITS table columns
  709 + nrows = len(results)
  710 + e_mean = gammalib.GFitsTableDoubleCol('E_MEAN', nrows)
  711 + e_min = gammalib.GFitsTableDoubleCol('E_MIN', nrows)
  712 + e_max = gammalib.GFitsTableDoubleCol('E_MAX', nrows)
  713 + flux_crab = gammalib.GFitsTableDoubleCol('FLUX_CRAB', nrows)
  714 + flux_photon = gammalib.GFitsTableDoubleCol('FLUX_PHOTON', nrows)
  715 + flux_energy = gammalib.GFitsTableDoubleCol('FLUX_ENERGY', nrows)
  716 + sensitivity = gammalib.GFitsTableDoubleCol('SENSITIVITY', nrows)
  717 + regcoeff = gammalib.GFitsTableDoubleCol('REGRESSION_COEFF', nrows)
  718 + nevents = gammalib.GFitsTableDoubleCol('NEVENTS', nrows)
  719 + npred = gammalib.GFitsTableDoubleCol('NPRED', nrows)
  720 + e_mean.unit('TeV')
  721 + e_min.unit('TeV')
  722 + e_max.unit('TeV')
  723 + flux_crab.unit('')
  724 + flux_photon.unit('ph/cm2/s')
  725 + flux_energy.unit('erg/cm2/s')
  726 + sensitivity.unit('erg/cm2/s')
  727 + regcoeff.unit('')
  728 + nevents.unit('counts')
  729 + npred.unit('counts')
  730 +
  731 + # Fill FITS table columns
  732 + for i, result in enumerate(results):
  733 + e_mean[i] = math.pow(10.0, result['loge'])
  734 + e_min[i] = result['emin']
  735 + e_max[i] = result['emax']
  736 + flux_crab[i] = result['crab_flux']
  737 + flux_photon[i] = result['photon_flux']
  738 + flux_energy[i] = result['energy_flux']
  739 + sensitivity[i] = result['sensitivity']
  740 + regcoeff[i] = result['regcoeff']
  741 + nevents[i] = result['nevents']
  742 + npred[i] = result['npred']
  743 +
  744 + # Initialise FITS Table with extension "SENSITIVITY"
  745 + table = gammalib.GFitsBinTable(nrows)
  746 + table.extname('SENSITIVITY')
  747 +
  748 + # Add keywors for compatibility with gammalib.GMWLSpectrum
  749 + table.card('INSTRUME', 'CTA', 'Name of Instrument')
  750 + table.card('TELESCOP', 'CTA', 'Name of Telescope')
  751 +
  752 + # Stamp header
  753 + self._stamp(table)
  754 +
  755 + # Add script keywords
  756 + table.card('OBJECT', self._srcname, 'Source for which sensitivity was estimated')
  757 + table.card('TYPE', self['type'].string(), 'Sensitivity type')
  758 + table.card('SIGMA', self['sigma'].real(), '[sigma] Sensitivity threshold')
  759 + table.card('MAX_ITER', self['max_iter'].integer(), 'Maximum number of iterations')
  760 + table.card('STAT', self['statistic'].string(), 'Optimization statistic')
  761 +
  762 + # Append filled columns to fits table
  763 + table.append(e_mean)
  764 + table.append(e_min)
  765 + table.append(e_max)
  766 + table.append(flux_crab)
  767 + table.append(flux_photon)
  768 + table.append(flux_energy)
  769 + table.append(sensitivity)
  770 + table.append(regcoeff)
  771 + table.append(nevents)
  772 + table.append(npred)
  773 +
  774 + # Create the FITS file now
  775 + self._fits = gammalib.GFits()
  776 + self._fits.append(table)
  777 +
  778 + # Return
  779 + return
  780 +
693 781
694 # Public methods 782 # Public methods
695 def run(self): 783 def run(self):
@@ -708,10 +796,7 @@ class cssens(ctools.csobservation): @@ -708,10 +796,7 @@ class cssens(ctools.csobservation):
708 for obs in self.obs(): 796 for obs in self.obs():
709 self._obs_ebounds.append(obs.events().ebounds().copy()) 797 self._obs_ebounds.append(obs.events().ebounds().copy())
710 798
711 - # Initialise script  
712 - colnames = ['loge', 'emin', 'emax', 'crab_flux', 'photon_flux',  
713 - 'energy_flux', 'sensitivity', 'regcoeff', 'nevents',  
714 - 'npred'] 799 + # Initialise results
715 results = [] 800 results = []
716 801
717 # Set test source model for this observation 802 # Set test source model for this observation
@@ -745,20 +830,50 @@ class cssens(ctools.csobservation): @@ -745,20 +830,50 @@ class cssens(ctools.csobservation):
745 else: 830 else:
746 for ieng in range(self._ebounds.size()): 831 for ieng in range(self._ebounds.size()):
747 832
748 - #Run analysis in energy bin 833 + # Run analysis in energy bin
749 result = self._e_bin(ieng) 834 result = self._e_bin(ieng)
750 835
751 # Append results 836 # Append results
752 results.append(result) 837 results.append(result)
753 838
754 - # Write out trial result  
755 - for ieng, result in enumerate(results):  
756 - ioutils.write_csv_row(self['outfile'].filename().url(), ieng,  
757 - colnames, result) 839 + # Create FITS file
  840 + self._create_fits(results)
758 841
759 # Return 842 # Return
760 return 843 return
761 844
  845 + def save(self):
  846 + """
  847 + Save sensitivity FITS file
  848 + """
  849 + # Write header
  850 + self._log_header1(gammalib.TERSE, 'Save sensitivity curve')
  851 +
  852 + # Continue only if FITS file is valid
  853 + if self._fits != None:
  854 +
  855 + # Get outmap parameter
  856 + outfile = self['outfile'].filename()
  857 +
  858 + # Log file name
  859 + self._log_value(gammalib.NORMAL, 'Sensitivity file', outfile.url())
  860 +
  861 + # Save sensitivity
  862 + self._fits.saveto(outfile, self['clobber'].boolean())
  863 +
  864 + # Return
  865 + return
  866 +
  867 + def sensitivity(self):
  868 + """
  869 + Return sensitivity FITS file
  870 +
  871 + Returns:
  872 + FITS file containing sensitivity curve
  873 + """
  874 + # Return
  875 + return self._fits
  876 +
762 877
763 # ======================== # 878 # ======================== #
764 # Main routine entry point # 879 # Main routine entry point #
doc/source/admin/release_history/2.0.rst
1 .. _2.0: 1 .. _2.0:
2 2
3 -ctools 2.0 release (7 May 2021)  
4 -=============================== 3 +ctools 2.0 release (11 May 2021)
  4 +================================
5 5
6 Overview 6 Overview
7 -------- 7 --------
@@ -44,6 +44,8 @@ Improvements @@ -44,6 +44,8 @@ Improvements
44 Replace ``std::tmpnam()`` by ``gammalib::tmpnam()`` function in :ref:`ctselect` 44 Replace ``std::tmpnam()`` by ``gammalib::tmpnam()`` function in :ref:`ctselect`
45 * [`3324 <https://cta-redmine.irap.omp.eu/issues/3324>`_] - 45 * [`3324 <https://cta-redmine.irap.omp.eu/issues/3324>`_] -
46 Remove implicit conversion warnings 46 Remove implicit conversion warnings
  47 +* [`1707 <https://cta-redmine.irap.omp.eu/issues/1707>`_] -
  48 + Replace ASCII output files by FITS files
47 49
48 50
49 New features 51 New features
doc/source/users/reference_manual/cssens.rst
@@ -42,17 +42,11 @@ bin/threshold will be performed in parallel over as many processes as the number @@ -42,17 +42,11 @@ bin/threshold will be performed in parallel over as many processes as the number
42 CPUs available on your machine. The maximum number of parallel processes can be set 42 CPUs available on your machine. The maximum number of parallel processes can be set
43 by the user through the ``nthreads`` hidden parameter. 43 by the user through the ``nthreads`` hidden parameter.
44 44
45 -:ref:`cssens` will generate an ASCII file in comma-separated value (CSV) format  
46 -containing the sensitivity as function of energy. The first row is a header row  
47 -providing the column names. The following rows provide the mean logarithmic  
48 -energy and the boundaries of the energy bin for which the sensitivity was  
49 -computed. They also provide the flux within the energy bin in Crab units, in  
50 -photons (ph/cm2/s) and in energy (erg/cm2/s). Finally, the sensitivity is given  
51 -as the test source spectrum evaluated at the mean logarithmic energy multiplied  
52 -by the energy squared (erg/cm2/s). 45 +:ref:`cssens` will generate a FITS file with a single extension that contains a
  46 +binary table with the sensitivity as function of energy.
53 47
54 -The sensitivity can be displayed using the ``show_sensitivity.py`` script in the  
55 -example folder. Matplotlib is required to execute the script. 48 +The sensitivity FITS file can be displayed using the ``show_sensitivity.py`` script
  49 +in the example folder. Matplotlib is required to execute the script.
56 50
57 51
58 General parameters 52 General parameters
@@ -81,7 +75,7 @@ General parameters @@ -81,7 +75,7 @@ General parameters
81 Average deadtime correction factor. 75 Average deadtime correction factor.
82 76
83 ``outfile [file]`` 77 ``outfile [file]``
84 - ASCII file containing the sensitivity values. 78 + Output sensitivity FITS file.
85 79
86 ``(offset = 0.0) [real]`` 80 ``(offset = 0.0) [real]``
87 Offset angle of source in field of view (in degrees). 81 Offset angle of source in field of view (in degrees).
examples/show_sensitivity.py
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 # ========================================================================== 2 # ==========================================================================
3 # Display differential sensitivity plot generated by cssens 3 # Display differential sensitivity plot generated by cssens
4 # 4 #
5 -# Copyright (C) 2015-2020 Juergen Knoedlseder 5 +# Copyright (C) 2015-2021 Juergen Knoedlseder
6 # 6 #
7 # This program is free software: you can redistribute it and/or modify 7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by 8 # it under the terms of the GNU General Public License as published by
@@ -36,6 +36,29 @@ import cscripts @@ -36,6 +36,29 @@ import cscripts
36 # ===================== # 36 # ===================== #
37 def read_sensitivity(filename): 37 def read_sensitivity(filename):
38 """ 38 """
  39 + Read sensitivity information
  40 +
  41 + Parameters
  42 + ----------
  43 + filename : str
  44 + Name of FITS or CSV file
  45 + """
  46 + # Read sensitivity information dependent on file type
  47 + fname = gammalib.GFilename(filename)
  48 + if fname.is_fits():
  49 + sensitivity = read_sensitivity_fits(filename)
  50 + else:
  51 + sensitivity = read_sensitivity_csv(filename)
  52 +
  53 + # Return sensitivity
  54 + return sensitivity
  55 +
  56 +
  57 +# =================================== #
  58 +# Read sensitivity data from CSV file #
  59 +# =================================== #
  60 +def read_sensitivity_csv(filename):
  61 + """
39 Read sensitivity information from CSV file 62 Read sensitivity information from CSV file
40 63
41 Parameters 64 Parameters
@@ -94,6 +117,61 @@ def read_sensitivity(filename): @@ -94,6 +117,61 @@ def read_sensitivity(filename):
94 return sensitivity 117 return sensitivity
95 118
96 119
  120 +# ==================================== #
  121 +# Read sensitivity data from FITS file #
  122 +# ==================================== #
  123 +def read_sensitivity_fits(filename):
  124 + """
  125 + Read sensitivity information from FITS file
  126 +
  127 + Parameters
  128 + ----------
  129 + filename : str
  130 + Name of CSV file
  131 +
  132 + Returns
  133 + -------
  134 + sensitivity : dict
  135 + Dictionary with sensitivity information
  136 + """
  137 + # Open FITS file
  138 + fits = gammalib.GFits(filename)
  139 +
  140 + # Get sensitivity table
  141 + table = fits.table('SENSITIVITY')
  142 +
  143 + # Get relevant columns
  144 + c_e_mean = table['E_MEAN']
  145 + c_e_min = table['E_MIN']
  146 + c_e_max = table['E_MAX']
  147 + c_sensitivity = table['SENSITIVITY']
  148 +
  149 + # Get mode
  150 + mode = table.card('TYPE').string()
  151 +
  152 + # Initialise vectors
  153 + energies = []
  154 + sensitivities = []
  155 +
  156 + # Fill vectors
  157 + nrows = table.nrows()
  158 + for row in range(nrows):
  159 + if mode == 'Differential':
  160 + energies.append(c_e_mean[row])
  161 + else:
  162 + energies.append(c_e_min[row])
  163 + sensitivities.append(c_sensitivity[row])
  164 +
  165 +
  166 + # Set dictionary
  167 + sensitivity = {'mode' : mode,
  168 + 'energy' : energies,
  169 + 'sensitivity' : sensitivities}
  170 +
  171 + # Return
  172 + return sensitivity
  173 +
  174 +
97 # ===================== # 175 # ===================== #
98 # Plot sensitivity data # 176 # Plot sensitivity data #
99 # ===================== # 177 # ===================== #
test/test_csphasecrv.py
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 # ========================================================================== 2 # ==========================================================================
3 # This scripts performs unit tests for the csphasecrv script. 3 # This scripts performs unit tests for the csphasecrv script.
4 # 4 #
5 -# Copyright (C) 2017-2020 Juergen Knoedlseder 5 +# Copyright (C) 2017-2021 Juergen Knoedlseder
6 # 6 #
7 # This program is free software: you can redistribute it and/or modify 7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by 8 # it under the terms of the GNU General Public License as published by
@@ -33,7 +33,7 @@ class Test(test): @@ -33,7 +33,7 @@ class Test(test):
33 This test class makes unit tests for the csphasecrv script by using it 33 This test class makes unit tests for the csphasecrv script by using it
34 from the command line and from Python. 34 from the command line and from Python.
35 """ 35 """
36 - 36 +
37 # Constructor 37 # Constructor
38 def __init__(self): 38 def __init__(self):
39 """ 39 """
@@ -280,7 +280,7 @@ class Test(test): @@ -280,7 +280,7 @@ class Test(test):
280 pcrv['phbinalg'] = 'LIN' 280 pcrv['phbinalg'] = 'LIN'
281 pcrv['phbins'] = 2 281 pcrv['phbins'] = 2
282 pcrv['method'] = '3D' 282 pcrv['method'] = '3D'
283 - pcrv['enumbins'] = 0 283 + pcrv['enumbins'] = 0 # Unbinned analysis
284 pcrv['emin'] = 1.0 284 pcrv['emin'] = 1.0
285 pcrv['emax'] = 100.0 285 pcrv['emax'] = 100.0
286 pcrv['outfile'] = 'csphasecrv_py6.fits' 286 pcrv['outfile'] = 'csphasecrv_py6.fits'
@@ -378,6 +378,6 @@ class Test(test): @@ -378,6 +378,6 @@ class Test(test):
378 378
379 # Close FITS file 379 # Close FITS file
380 fits.close() 380 fits.close()
381 - 381 +
382 # Return 382 # Return
383 return 383 return
test/test_cssens.py
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 # ========================================================================== 2 # ==========================================================================
3 # This scripts performs unit tests for the cssens script 3 # This scripts performs unit tests for the cssens script
4 # 4 #
5 -# Copyright (C) 2016-2018 Juergen Knoedlseder 5 +# Copyright (C) 2016-2021 Juergen Knoedlseder
6 # 6 #
7 # This program is free software: you can redistribute it and/or modify 7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by 8 # it under the terms of the GNU General Public License as published by
@@ -77,7 +77,7 @@ class Test(test): @@ -77,7 +77,7 @@ class Test(test):
77 ' srcname="Crab"'+ \ 77 ' srcname="Crab"'+ \
78 ' caldb="'+self._caldb+'" irf="'+self._irf+'"'+ \ 78 ' caldb="'+self._caldb+'" irf="'+self._irf+'"'+ \
79 ' duration=180.0 rad=3.0 emin=1.0 emax=10.0 bins=1'+ \ 79 ' duration=180.0 rad=3.0 emin=1.0 emax=10.0 bins=1'+ \
80 - ' outfile="cssens_cmd1.dat"'+ \ 80 + ' outfile="cssens_cmd1.fits"'+ \
81 ' logfile="cssens_cmd1.log" chatter=1' 81 ' logfile="cssens_cmd1.log" chatter=1'
82 82
83 # Check if execution was successful 83 # Check if execution was successful
@@ -85,14 +85,14 @@ class Test(test): @@ -85,14 +85,14 @@ class Test(test):
85 'Check successful execution from command line') 85 'Check successful execution from command line')
86 86
87 # Check result file 87 # Check result file
88 - self._check_result_file('cssens_cmd1.dat') 88 + self._check_result_file('cssens_cmd1.dat', 1)
89 89
90 # Setup cssens command 90 # Setup cssens command
91 cmd = cssens+' inmodel="model_that_does_not_exist.xml"'+ \ 91 cmd = cssens+' inmodel="model_that_does_not_exist.xml"'+ \
92 ' srcname="Crab"'+ \ 92 ' srcname="Crab"'+ \
93 ' caldb="'+self._caldb+'" irf="'+self._irf+'"'+ \ 93 ' caldb="'+self._caldb+'" irf="'+self._irf+'"'+ \
94 ' duration=180.0 rad=3.0 emin=1.0 emax=10.0 bins=1'+ \ 94 ' duration=180.0 rad=3.0 emin=1.0 emax=10.0 bins=1'+ \
95 - ' outfile="cssens_cmd2.dat"'+ \ 95 + ' outfile="cssens_cmd2.fits"'+ \
96 ' logfile="cssens_cmd2.log" debug=yes chatter=2' 96 ' logfile="cssens_cmd2.log" debug=yes chatter=2'
97 97
98 # Check if execution failed 98 # Check if execution failed
@@ -118,12 +118,12 @@ class Test(test): @@ -118,12 +118,12 @@ class Test(test):
118 sens['srcname'] = 'Crab' 118 sens['srcname'] = 'Crab'
119 sens['caldb'] = self._caldb 119 sens['caldb'] = self._caldb
120 sens['irf'] = self._irf 120 sens['irf'] = self._irf
121 - sens['outfile'] = 'cssens_py1.dat'  
122 sens['duration'] = 180.0 121 sens['duration'] = 180.0
123 sens['rad'] = 3.0 122 sens['rad'] = 3.0
124 sens['emin'] = 1.0 123 sens['emin'] = 1.0
125 sens['emax'] = 10.0 124 sens['emax'] = 10.0
126 sens['bins'] = 2 125 sens['bins'] = 2
  126 + sens['outfile'] = 'cssens_py1.fits'
127 sens['logfile'] = 'cssens_py1.log' 127 sens['logfile'] = 'cssens_py1.log'
128 sens['chatter'] = 4 128 sens['chatter'] = 4
129 129
@@ -132,8 +132,8 @@ class Test(test): @@ -132,8 +132,8 @@ class Test(test):
132 sens.run() 132 sens.run()
133 sens.save() 133 sens.save()
134 134
135 - # Check pull distribution file  
136 - self._check_result_file('cssens_py1.dat',nrows=3) 135 + # Check sensitivity file
  136 + self._check_result_file('cssens_py1.fits', 2)
137 137
138 # Set-up cssens for integral sensitivity computation without 138 # Set-up cssens for integral sensitivity computation without
139 # multiprocessing 139 # multiprocessing
@@ -143,13 +143,13 @@ class Test(test): @@ -143,13 +143,13 @@ class Test(test):
143 sens['srcname'] = 'Crab' 143 sens['srcname'] = 'Crab'
144 sens['caldb'] = self._caldb 144 sens['caldb'] = self._caldb
145 sens['irf'] = self._irf 145 sens['irf'] = self._irf
146 - sens['outfile'] = 'cssens_py2.dat'  
147 sens['duration'] = 180.0 146 sens['duration'] = 180.0
148 sens['rad'] = 3.0 147 sens['rad'] = 3.0
149 sens['emin'] = 1.0 148 sens['emin'] = 1.0
150 sens['emax'] = 10.0 149 sens['emax'] = 10.0
151 sens['bins'] = 1 150 sens['bins'] = 1
152 sens['type'] = 'Integral' 151 sens['type'] = 'Integral'
  152 + sens['outfile'] = 'cssens_py2.fits'
153 sens['logfile'] = 'cssens_py2.log' 153 sens['logfile'] = 'cssens_py2.log'
154 sens['chatter'] = 4 154 sens['chatter'] = 4
155 sens['nthreads'] = 1 155 sens['nthreads'] = 1
@@ -157,8 +157,8 @@ class Test(test): @@ -157,8 +157,8 @@ class Test(test):
157 # Execute cssens script 157 # Execute cssens script
158 sens.execute() 158 sens.execute()
159 159
160 - # Check pull distribution file  
161 - self._check_result_file('cssens_py2.dat') 160 + # Check sensitivity file
  161 + self._check_result_file('cssens_py2.fits', 1)
162 162
163 # Return 163 # Return
164 return 164 return
@@ -178,12 +178,12 @@ class Test(test): @@ -178,12 +178,12 @@ class Test(test):
178 sens['srcname'] = 'Crab' 178 sens['srcname'] = 'Crab'
179 sens['caldb'] = self._caldb 179 sens['caldb'] = self._caldb
180 sens['irf'] = self._irf 180 sens['irf'] = self._irf
181 - sens['outfile'] = 'cssens_py1_pickle.dat'  
182 sens['duration'] = 180.0 181 sens['duration'] = 180.0
183 sens['rad'] = 3.0 182 sens['rad'] = 3.0
184 sens['emin'] = 1.0 183 sens['emin'] = 1.0
185 sens['emax'] = 10.0 184 sens['emax'] = 10.0
186 sens['bins'] = 2 185 sens['bins'] = 2
  186 + sens['outfile'] = 'cssens_py1_pickle.fits'
187 sens['logfile'] = 'cssens_py1_pickle.log' 187 sens['logfile'] = 'cssens_py1_pickle.log'
188 sens['chatter'] = 4 188 sens['chatter'] = 4
189 189
@@ -196,23 +196,24 @@ class Test(test): @@ -196,23 +196,24 @@ class Test(test):
196 obj.save() 196 obj.save()
197 197
198 # Check result file 198 # Check result file
199 - self._check_result_file('cssens_py1_pickle.dat',nrows=3) 199 + self._check_result_file('cssens_py1_pickle.fits', 2)
200 200
201 # Return 201 # Return
202 return 202 return
203 203
204 # Check result file 204 # Check result file
205 - def _check_result_file(self, filename, nrows=2): 205 + def _check_result_file(self, filename, nrows):
206 """ 206 """
207 Check result file 207 Check result file
208 """ 208 """
209 - # Open result file as CSV file  
210 - results = gammalib.GCsv(filename, ',') 209 + # Open result file as FITS file
  210 + fits = gammalib.GFits(filename)
  211 + table = fits.table('SENSITIVITY')
211 212
212 # Check dimensions 213 # Check dimensions
213 - self.test_value(results.nrows(), nrows, 214 + self.test_value(table.nrows(), nrows,
214 'Check number of rows in sensitivity file') 215 'Check number of rows in sensitivity file')
215 - self.test_value(results.ncols(), 10, 216 + self.test_value(table.ncols(), 10,
216 'Check number of columns in sensitivity file') 217 'Check number of columns in sensitivity file')
217 218
218 # Return 219 # Return