ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csiactobs.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # ==========================================================================
3 # Generates an IACT observation definition XML file
4 #
5 # Copyright (C) 2015-2022 Michael Mayer
6 #
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
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #
20 # ==========================================================================
21 import sys
22 import os
23 import json
24 import gammalib
25 import ctools
26 
27 
28 # =============== #
29 # csiactobs class #
30 # =============== #
31 class csiactobs(ctools.cscript):
32  """
33  Generates an IACT observation definition XML file
34 
35  This class implements the creation of a observation xml file for IACT
36  data analysis. This class is dedicated for use inside a IACT
37  Collaboration, i.e. it can only be used if you have access to IACT data
38  in FITS format. The FITS data has to be structured and in the format
39  described here:
40  http://gamma-astro-data-formats.readthedocs.org/en/latest/
41  """
42 
43  # Constructor
44  def __init__(self, *argv):
45  """
46  Constructor
47  """
48  # Initialise application by calling the base class constructor
49  self._init_cscript(self.__class__.__name__, ctools.__version__, argv)
50 
51  # Initialise some members
52  self._obs = gammalib.GObservations()
53  self._ebounds = gammalib.GEbounds()
54  self._datapath = os.getenv('VHEFITS','')
55  self._prodname = ''
56  self._xml = gammalib.GXml()
57  self._models = gammalib.GModels()
58  self._runlist = []
59  self._runlistfile = gammalib.GFilename()
60  self._bkgpars = 0
61  self._master_indx = ''
62  self._use_bkg_scale = False
63  self._ev_hiera = ['']
64  self._aeff_hiera = ['']
65  self._psf_hiera = ['']
66  self._bkg_hiera = ['']
67  self._edisp_hiera = ['']
68  self._bkg_mod_hiera = ['']
69  self._bkg_gauss_norm = 1.0
70  self._bkg_gauss_index = 0.0
71  self._bkg_gauss_sigma = 1.0
72  self._bkg_aeff_index = 0.0
73  self._bkg_aeff_norm = 1.0
74  self._bkg_range_factor = 1.0
75  self._hdu_index = ''
76  self._obs_index = ''
77  self._subdir = ''
78  self._debug = False
79 
80  # Initialise empty observation definition XML file
81  self._xml.append(gammalib.GXmlElement('observation_list '
82  'title="observation list"'))
83 
84  # Append an observation list to XML instance
85  self._xml.append(gammalib.GXmlElement('observation_list title="observation list"'))
86  self._xml_obslist = self._xml.element('observation_list', 0)
87 
88  # Return
89  return
90 
91 
92  # Private methods
93  def _get_parameters(self):
94  """
95  Get parameters from parfile and setup the observation
96  """
97  # Query datapath. If the parameter is not NONE then use it, otherwise
98  # use the datapath from the VHEFITS environment variable
99  datapath = self['datapath'].string()
100  if gammalib.toupper(datapath) != 'NONE':
101  self._datapath = datapath
102  else:
103  self._datapath = os.getenv('VHEFITS','')
104 
105  # Expand environment
106  self._datapath = gammalib.expand_env(self._datapath)
107 
108  # Query input parameters
109  self['inmodel'].query()
110 
111  # Read FITS production
112  self._prodname = self['prodname'].string()
113 
114  # Read runlist file if list not already filled
115  if len(self._runlist) == 0:
116 
117  # Get file name
118  self._runlistfile = self['infile'].filename()
119 
120  # Read runlist from file
121  runfile = open(self._runlistfile.url())
122  for line in runfile.readlines():
123  if len(line) == 0:
124  continue
125  if line[0] == '#':
126  continue
127  if len(line.split()) > 0:
128  self._runlist.append(line.split()[0])
129  runfile.close()
130 
131  # Read number of background parameters
132  self._bkgpars = self['bkgpars'].integer()
133 
134  # Query ahead output parameters
135  if self._read_ahead():
136  self['outmodel'].filename()
137  self['outobs'].filename()
138 
139  # Master index file name
140  self._master_indx = self['master_indx'].string()
141 
142  # Read flag for background scaling factor
143  self._use_bkg_scale = self['bkg_scale'].boolean()
144 
145  # Read hierarchy of file loading
146  self._ev_hiera = self['ev_hiera'].string().split('|')
147  self._aeff_hiera = self['aeff_hiera'].string().split('|')
148  self._psf_hiera = self['psf_hiera'].string().split('|')
149  self._bkg_hiera = self['bkg_hiera'].string().split('|')
150  self._edisp_hiera = self['edisp_hiera'].string().split('|')
151  self._bkg_mod_hiera = self['bkg_mod_hiera'].string().split('|')
152 
153  # Read hidden background parameters
154  self._bkg_gauss_norm = self['bkg_gauss_norm'].real()
155  self._bkg_gauss_index = self['bkg_gauss_index'].real()
156  self._bkg_gauss_sigma = self['bkg_gauss_sigma'].real()
157  self._bkg_aeff_index = self['bkg_aeff_index'].real()
158  self._bkg_aeff_norm = self['bkg_aeff_norm'].real()
159  self._bkg_range_factor = self['bkg_range_factor'].real()
160 
161  # Open master index file and look for prodname
162  master_file = os.path.join(self._datapath, self._master_indx)
163  if not os.path.isfile(master_file):
164  raise RuntimeError('FITS data store not available. No master '
165  'index file found. Make sure the file is '
166  'copied from the server and your datapath '
167  'is set correctly.')
168 
169  # Open and load JSON file
170  json_data = open(master_file).read()
171  data = json.loads(json_data)
172  if not 'datasets' in data:
173  raise RuntimeError('Key "datasets" not available in master '
174  'index file.')
175 
176  # Get configurations
177  configs = data['datasets']
178 
179  # Initialise HDUs
180  self._hdu_index = self._obs_index = ''
181 
182  # Get HDUs
183  for config in configs:
184 
185  # Check if prodname is present
186  if self._prodname == config['name']:
187  self._hdu_index = str(os.path.join(self._datapath,
188  config['hduindx']))
189  self._obs_index = str(os.path.join(self._datapath,
190  config['obsindx']))
191 
192  # Leave loop if index file names were found
193  break
194 
195  # Check index files
196  if self._hdu_index == '' or self._obs_index == '':
197  raise RuntimeError('*** ERROR: FITS data store "'+self._prodname+
198  '" not available. Run csiactdata to get a list '
199  'of available storage names')
200 
201  # Check HDU names
202  filename = gammalib.GFilename(self._hdu_index+'[HDU_INDEX]')
203  if not filename.is_fits():
204  raise RuntimeError('*** ERROR: HDU index file "'+self._hdu_index+
205  '[HDU_INDEX]" for FITS data store "'+
206  self._prodname+'" not available. Check your '
207  'master index file or run csiactdata to get '
208  'a list of available storage names.')
209 
210  # Check for existence of 'BKG_SCALE' in the observation index file if
211  # required
212  if self._use_bkg_scale:
213 
214  # Create filename
215  filename = gammalib.GFilename(self._obs_index+'[OBS_INDEX]')
216 
217  # Check if it is a FITS file
218  if filename.is_fits():
219 
220  # Open FITS file
221  fits = gammalib.GFits(self._obs_index)
222 
223  # Check if column "BKG_SCALE" is found and signal its possible
224  # usage
225  if not fits['OBS_INDEX'].contains('BKG_SCALE'):
226  self._use_bkg_scale = False
227 
228  # Close FITS file
229  fits.close()
230 
231  else:
232  # Signal that there is no background scale
233  self._use_bkg_scale = False
234 
235  # Create base data directory from hdu index file location
236  self._subdir = os.path.dirname(self._hdu_index)
237  self._debug = False # Debugging in client tools
238 
239  # Write input parameters into logger
240  self._log_parameters(gammalib.TERSE)
241 
242  # Return
243  return
244 
245  def _background_spectrum(self, prefactor, index, emin=0.01, emax=100.0):
246  """
247  Create a background spectrum model dependent on user parameters
248 
249  Parameters
250  ----------
251  prefactor : float
252  Power law prefactor of spectral model
253  index : float
254  Power law index of spectral model
255  emin : float
256  Minimum energy (in case a spectral node function is required)
257  emax : float
258  Maximum energy (in case a spectral node function is required)
259 
260  Returns
261  -------
262  spec : `~gammalib.GModelSpectral()`
263  Spectral model for the background shape
264  """
265  # Handle constant spectral model
266  if index == 0.0 and self._bkgpars <= 1:
267  spec = gammalib.GModelSpectralConst()
268 
269  # Set parameter range
270  spec['Normalization'].min(prefactor / self._bkg_range_factor)
271  spec['Normalization'].max(prefactor * self._bkg_range_factor)
272  spec['Normalization'].value(prefactor)
273 
274  # Freeze or release normalisation parameter
275  if self._bkgpars == 0:
276  spec['Normalization'].fix()
277  else:
278  spec['Normalization'].free()
279 
280  else:
281 
282  # Create power law model
283  if self._bkgpars <= 2:
284 
285  # Set Power Law model with arbitrary pivot energy
286  pivot = gammalib.GEnergy(1.0,'TeV')
287  spec = gammalib.GModelSpectralPlaw(prefactor, index, pivot)
288 
289  # Set parameter ranges
290  spec[0].min(prefactor / self._bkg_range_factor)
291  spec[0].max(prefactor * self._bkg_range_factor)
292  spec[1].scale(1)
293  spec[1].min(-5.0)
294  spec[1].max(5.0)
295 
296  # Set number of free parameters
297  if self._bkgpars == 0:
298  spec[0].fix()
299  spec[1].fix()
300  elif self._bkgpars == 1:
301  spec[0].free()
302  spec[1].fix()
303  else:
304  spec[0].free()
305  spec[1].free()
306 
307  else:
308 
309  # Create reference powerlaw
310  pivot = gammalib.GEnergy(1.0,'TeV')
311  plaw = gammalib.GModelSpectralPlaw(prefactor, index, pivot)
312 
313  # Create spectral model and energy values
314  spec = gammalib.GModelSpectralNodes()
315 
316  # Create logarithmic energy nodes
317  bounds = gammalib.GEbounds(self._bkgpars,
318  gammalib.GEnergy(emin,'TeV'),
319  gammalib.GEnergy(emax,'TeV'))
320 
321  # Loop over bounds and set intensity value
322  for i in range(bounds.size()):
323  energy = bounds.elogmean(i)
324  value = plaw.eval(energy)
325 
326  # Append energy, value - tuple to node function
327  spec.append(energy, value)
328 
329  # Loop over parameters
330  for par in spec:
331 
332  # Fix energy nodes
333  if 'Energy' in par.name():
334  par.fix()
335 
336  # Release intensity nodes
337  elif 'Intensity' in par.name():
338  value = par.value()
339  par.scale(value)
340  par.min(value / self._bkg_range_factor)
341  par.max(value * self._bkg_range_factor)
342 
343  # Return spectrum
344  return spec
345 
346  def _iact_background(self, telescope, obs_id, bkg_scale, bkgtype,
347  emin=0.01, emax=100):
348  """
349  Create an IACT background model
350 
351  Parameters
352  ----------
353  telescope : str
354  Name of telescope
355  obs_id : str
356  Observation ID
357  bkg_scale : float
358  Background scaling factor
359  bkgtype : str
360  Type of background (irf,aeff, or gauss)
361 
362  Returns
363  -------
364  model : `~gammalib.GModelData()`
365  Background model for IACT observation
366  """
367  # Handle IrfBackground
368  if bkgtype == 'irf':
369 
370  # Set parameters to have a constant spectral model
371  prefactor = 1.0
372  index = 0.0
373  prefactor *= bkg_scale
374 
375  # Create background spectrum
376  spec = self._background_spectrum(prefactor, index, emin, emax)
377 
378  # Create background model
379  bck = gammalib.GCTAModelIrfBackground(spec)
380 
381  # Set AeffBackground
382  elif bkgtype == 'aeff':
383 
384  # Set background components
385  prefactor = bkg_scale * self._bkg_aeff_norm
386  spec = self._background_spectrum(prefactor,
387  self._bkg_aeff_index,
388  emin, emax)
389 
390  # Create background model
391  bck = gammalib.GCTAModelAeffBackground(spec)
392 
393  # Set Gaussian Background
394  elif bkgtype == 'gauss':
395 
396  # Set background components
397  prefactor = bkg_scale * self._bkg_gauss_norm
398  spec = self._background_spectrum(prefactor,
399  self._bkg_gauss_index,
400  emin, emax)
401  radial = gammalib.GCTAModelRadialGauss(self._bkg_gauss_sigma)
402 
403  # Create background model
404  bck = gammalib.GCTAModelRadialAcceptance(radial, spec)
405 
406  else:
407  msg = 'Background type "'+bkgtype+'" unsupported'
408  raise RuntimeError(msg)
409 
410  # Copy model
411  model = bck.clone()
412 
413  # Assign specific run id
414  model.ids(str(obs_id))
415 
416  # Assign instrument
417  model.instruments(telescope)
418 
419  # Set name (arbitrary)
420  model.name('bkg_'+str(obs_id))
421 
422  # Turn off TS calculation for background model
423  model.tscalc(False)
424 
425  # Return model
426  return model
427 
428  def _append_inmodels(self):
429  """
430  Append input models
431  """
432  # If there are models provided by "inmodels" then append them now to
433  # the model container
434  if self['inmodel'].is_valid():
435 
436  # Get filename
437  filename = self['inmodel'].filename().url()
438 
439  # Log header and input model file
440  self._log_header1(gammalib.TERSE, 'Append input models')
441  self._log_value(gammalib.NORMAL, 'Input model file', filename)
442 
443  # Load input models
444  models = gammalib.GModels(filename)
445 
446  # Loop over input models and append them to the model container
447  for model in models:
448  self._models.append(model)
449  self._log_value(gammalib.NORMAL, 'Append model',
450  '"'+model.name()+'"')
451 
452  # Return
453  return
454 
455  def _write_summary(self):
456  """
457  Write observation summary
458  """
459  # Log header
460  self._log_header1(gammalib.NORMAL, 'Observation summary')
461 
462  # Set energy range dependent on whether boundaries exist or
463  # not
464  if self._ebounds.size() > 0:
465  erange = '%s - %s' % (str(self._ebounds.emin()),
466  str(self._ebounds.emax()))
467  else:
468  erange = 'not available'
469 
470  # Log energy range
471  self._log_value(gammalib.NORMAL, 'Energy range', erange)
472 
473  # Return
474  return
475 
476  def _get_filename(self, hdu, hierarchy, formats):
477  """
478  Retrieves a filename from a hdu index
479 
480  Parameters
481  ----------
482  hdu : `~gammalib.GFitsTable`
483  HDU FITS table
484  hierarchy : list of str
485  List of strings containing the hierarchy how to look for files
486  formats : list of str
487  File formats available for this observation
488 
489  Returns
490  -------
491  filename : str
492  Filename of requested file
493  """
494 
495  # Initialise filename
496  filename = ''
497 
498  # Handle hierarchies
499  index = -1
500 
501  # Loop over possible formats
502  for version in hierarchy:
503 
504  # Count number of same available versions
505  n = formats.count(version)
506 
507  # Leave loop and store index of first occurence
508  if n > 0:
509  index = formats.index(version)
510  break
511 
512  # Build filename from information stored in hduindx_hdu
513  if index >= 0:
514  filename = os.path.join(self._subdir,
515  hdu['FILE_DIR'][index],
516  hdu['FILE_NAME'][index])
517  filehdu = hdu['HDU_NAME'][index]
518  filename += '['+filehdu+']'
519 
520  # Return filename
521  return filename
522 
523  def _is_present(self, filename, obs_id, filetype):
524  """
525  Checks if a filename is present
526 
527  Parameters
528  ----------
529  filename : str
530  Filename
531  obs_id : int
532  Observation ID
533  filetype : str
534  Type of file
535 
536  Returns
537  -------
538  present : bool
539  True if filename is present, False otherwise
540  """
541 
542  # Initialise return value
543  present = True
544 
545  # Create filename instance
546  fname = gammalib.GFilename(filename)
547 
548  # Check if file is empty
549  if fname.is_empty():
550  msg = 'Skipping observation "%s": No %s found' % (obs_id, filetype)
551  self._log_string(gammalib.NORMAL, msg)
552  present = False
553 
554  # Check if file exists
555  elif not fname.exists():
556  msg = ('Skipping observation "%s": %s "%s" does not exist' %
557  (obs_id, filetype, filename))
558  self._log_string(gammalib.NORMAL, msg)
559  present = False
560 
561  # Return present flag
562  return present
563 
564 
565  # Public methods
566  def process(self):
567  """
568  Process the script
569  """
570  # Get parameters
571  self._get_parameters()
572 
573  # Clear models
574  self._models.clear()
575 
576  # Log output
577  msg = 'Looping over %d runs' % len(self._runlist)
578  self._log_header1(gammalib.TERSE, msg)
579 
580  # Initialise energy range values for logging
581  self._ebounds.clear()
582 
583  # Loop over runs
584  for obs_id in self._runlist:
585 
586  # Create selection string
587  obs_selection = '[OBS_ID=='+str(obs_id)+']'
588 
589  # Open HDU index file
590  hduindx = gammalib.GFits(self._hdu_index+'[HDU_INDEX]'+
591  obs_selection)
592  hduindx_hdu = hduindx['HDU_INDEX']
593 
594  # Initialise types and formats
595  formats = []
596  for i in range(hduindx_hdu.nrows()):
597  formats.append(hduindx_hdu['HDU_CLASS'][i])
598 
599  # Retrieve filenames
600  eventfile = self._get_filename(hduindx_hdu, self._ev_hiera, formats)
601  aefffile = self._get_filename(hduindx_hdu, self._aeff_hiera, formats)
602  psffile = self._get_filename(hduindx_hdu, self._psf_hiera, formats)
603  edispfile = self._get_filename(hduindx_hdu, self._edisp_hiera, formats)
604  bkgfile = self._get_filename(hduindx_hdu, self._bkg_hiera, formats)
605 
606  # Check for presence of required event file
607  if not self._is_present(eventfile, obs_id, "event file"):
608  continue
609 
610  # Check for presence of required effective area file
611  if not self._is_present(aefffile, obs_id, "effective area file"):
612  continue
613 
614  # Check for presence of required psf file
615  if not self._is_present(psffile, obs_id, "PSF file"):
616  continue
617 
618  # Check for optional energy dispersion file
619  if not gammalib.GFilename(edispfile).exists():
620 
621  # Print warning that edisp cannot be used for this observation
622  msg = ('Warning: observation "%s" has no energy dispersion '
623  'information' % obs_id)
624  self._log_string(gammalib.NORMAL, msg)
625 
626  # Set energy dispersion file as empty
627  edispfile = ''
628 
629  # Create a copy of background model hierarchy for this run
630  run_bkg_mod_hierarchy = list(self._bkg_mod_hiera)
631 
632  # Check if background file is available
633  # Remove IRF background from hierarchy if file doesnt exist
634  if not gammalib.GFilename(bkgfile).exists():
635 
636  # Set background file as empty
637  bkgfile = ''
638 
639  # Check for IRF background in background model hierarchy
640  if 'irf' in run_bkg_mod_hierarchy:
641 
642  # Remove IRF background if file doesnt exist
643  run_bkg_mod_hierarchy.remove('irf')
644 
645  # Print warning that edisp cannot be used for this
646  # observation
647  msg = ('Warning: observation "%s" has no background '
648  'information (IRF background cannot be used)' %
649  obs_id)
650  self._log_string(gammalib.NORMAL, msg)
651 
652  # Check if background model hierarchy is empty
653  if len(run_bkg_mod_hierarchy) == 0:
654 
655  # Skip observation if no background can be used
656  msg = ('Skipping observation "%s": No background can be '
657  'used' % obs_id)
658  self._log_string(gammalib.NORMAL, msg)
659  continue
660 
661  else:
662  # Log if we fall back to next background approach
663  msg = ('Observation "%s": Falling back to background "%s"' %
664  (obs_id, run_bkg_mod_hierarchy[0]))
665  self._log_string(gammalib.NORMAL, msg)
666 
667  # Close hdu index file
668  hduindx.close()
669 
670  # Initialise background scale
671  bkg_scale = 1.0
672 
673  # Check if background scale should be used
674  if self._use_bkg_scale:
675 
676  # Read background scale from fits file
677  obsindx = gammalib.GFits(self._obs_index+'[OBS_INDEX]'+
678  obs_selection)
679  bkg_scale = obsindx['OBS_INDEX']['BKG_SCALE'][0]
680  obsindx.close()
681 
682  # Open event FITS file
683  fits = gammalib.GFits(eventfile)
684 
685  # Get object and telescope strings from event hdu
686  events = fits[gammalib.GFilename(eventfile).extname()]
687  object_name = events.string('OBJECT')
688  telescope = events.string('TELESCOP')
689 
690  # Close FITS file
691  fits.close()
692 
693  # Open effective area file to look for threshold
694  aeff_fits = gammalib.GFits(aefffile)
695  aeff_table = aeff_fits[gammalib.GFilename(aefffile).extname()]
696 
697  # Set energy range from header keyword if present
698  if aeff_table.has_card('LO_THRES') and aeff_table.has_card('HI_THRES'):
699 
700  # Get safe energy range
701  run_emin = gammalib.GEnergy(aeff_table.real('LO_THRES'), 'TeV')
702  run_emax = gammalib.GEnergy(aeff_table.real('HI_THRES'), 'TeV')
703 
704  # Append to ebounds
705  self._ebounds.append(run_emin, run_emax)
706 
707  else:
708  # Set default values for energy range
709  run_emin = gammalib.GEnergy(10,'GeV')
710  run_emax = gammalib.GEnergy(100,'TeV')
711 
712  # Close Aeff fits file
713  aeff_fits.close()
714 
715  # Append instrumental background model
716  self._models.append(self._iact_background(telescope,
717  obs_id,
718  bkg_scale,
719  run_bkg_mod_hierarchy[0],
720  run_emin.TeV(),
721  run_emax.TeV()))
722 
723  # Append observation to XML and set attributes
724  obs = self._xml_obslist.append('observation')
725  obs.attribute('name', object_name)
726  obs.attribute('id', str(obs_id))
727  obs.attribute('instrument', telescope)
728 
729  # Append event file
730  ev = gammalib.GXmlElement('parameter name="EventList"')
731  ev.attribute('file', eventfile)
732 
733  # Append effective area
734  aeff = gammalib.GXmlElement('parameter name="EffectiveArea"')
735  aeff.attribute('file', aefffile)
736 
737  # Append PSF
738  psf = gammalib.GXmlElement('parameter name="PointSpreadFunction"')
739  psf.attribute('file', psffile)
740 
741  # Append energy dispersion
742  edisp = gammalib.GXmlElement('parameter name="EnergyDispersion"')
743  edisp.attribute('file', edispfile)
744 
745  # Append background
746  bck = gammalib.GXmlElement('parameter name="Background"')
747  bck.attribute('file', bkgfile)
748 
749  # Assign events and IRFs to observation
750  obs.append(ev)
751  obs.append(aeff)
752  obs.append(psf)
753  obs.append(edisp)
754  obs.append(bck)
755 
756  # Log the observation ID and object name that has been appended
757  self._log_value(gammalib.NORMAL, 'Append observation', '%s ("%s")' %
758  (obs_id, object_name))
759 
760  # Log the file names of the event and response files
761  self._log_value(gammalib.EXPLICIT, ' Event file', eventfile)
762  self._log_value(gammalib.EXPLICIT, ' Effective area file', aefffile)
763  self._log_value(gammalib.EXPLICIT, ' Point spread function file',
764  psffile)
765  self._log_value(gammalib.EXPLICIT, ' Energy dispersion file',
766  edispfile)
767  self._log_value(gammalib.EXPLICIT, ' Background file', bkgfile)
768  if self._use_bkg_scale:
769  self._log_value(gammalib.EXPLICIT, ' Background scale',
770  bkg_scale)
771 
772  # Append models provided by "inmodel" to the model container
773  self._append_inmodels()
774 
775  # Write observation summary
776  self._write_summary()
777 
778  # Write warning in no observation could be used from runlist
779  if self._xml_obslist.size() == 0:
780  self._log_string(gammalib.NORMAL, 'WARNING: No observation was '
781  'appended from specified runlist')
782 
783  # Return
784  return
785 
786  def save(self):
787  """
788  Save observation definition and model definition XML file
789  """
790  # Write header
791  self._log_header1(gammalib.TERSE, 'Save XML files')
792 
793  # Get filenames
794  outobs = self['outobs'].filename()
795  outmodel = self['outmodel'].filename()
796 
797  # Log observation definition file name
798  self._log_value(gammalib.NORMAL, 'Obs. definition XML file',
799  outobs.url())
800 
801  # Save observation definition XML file
802  self._xml.save(outobs.url())
803 
804  # Log model definition file name
805  self._log_value(gammalib.NORMAL, 'Model definiton XML file',
806  outmodel.url())
807 
808  # Save model XML file
809  self._models.save(outmodel.url())
810 
811  # Return
812  return
813 
814  def runlist(self, runlist):
815  """
816  Set runlist
817 
818  Parameters
819  ----------
820  runlist : list
821  List of observation IDs
822 
823  Raises
824  ------
825  RuntimeError
826  Input runlist is not a Python list
827  """
828  # If the runlist is convertable to a Python list then store the
829  # runlist in the class
830  try:
831  self._runlist = list(runlist)
832 
833  # ... otherwise raise an exception
834  except:
835  raise RuntimeError('Argument is not a Python list')
836 
837  # Return
838  return
839 
840  def ebounds(self):
841  """
842  Return runlist energy boundaries
843  """
844  # Return energy boundaries
845  return self._ebounds
846 
847  def obs(self):
848  """
849  Return observations container
850  """
851  # Clear observations
852  self._obs.clear()
853 
854  # Read observations if XML is filled
855  if self._xml.size():
856  self._obs.read(self._xml)
857 
858  # Assign models
859  self._obs.models(self._models)
860 
861  # Return observations
862  return self._obs
863 
864 
865 # ======================== #
866 # Main routine entry point #
867 # ======================== #
868 if __name__ == '__main__':
869 
870  # Create instance of application
871  app = csiactobs(sys.argv)
872 
873  # Execute application
874  app.execute()