ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csobs2caldb.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # ==========================================================================
3 # Creates a calibration database entry for an IACT observation
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 datetime
24 import gammalib
25 import ctools
26 from cscripts import calutils
27 
28 
29 # ================= #
30 # csobs2caldb class #
31 # ================= #
32 class csobs2caldb(ctools.csobservation):
33  """
34  Creates a calibration database entry for an IACT observation.
35 
36  The creation of a calibration database entry is useful for performing
37  simulations for current Imaging Air Cherenkov Telescopes (IACTs).
38  The class takes an observation definition XML file as input and uses
39  one observation to create a calibration database entry. By default
40  the first observation will be used, but the user can specify the
41  index of any observation using the hidden "index" parameter.
42  """
43 
44  # Constructor
45  def __init__(self, *argv):
46  """
47  Constructor
48  """
49  # Initialise application by calling the appropriate class constructor
50  self._init_csobservation(self.__class__.__name__, ctools.__version__, argv)
51 
52  # Initialise members
53  self._observation = gammalib.GCTAObservation()
54  self._mission = 'cta'
55  self._caldb = 'cta'
56  self._outfile = gammalib.GFilename('irf_file.fits')
57  self._base_dir = ''
58  self._cal_dir = ''
59  self._rsp_dir = ''
60  self._caldb_inx = gammalib.GFits()
61  self._irf_fits = gammalib.GFits()
62 
63  # Return
64  return
65 
66 
67  # Private methods
68  def _get_parameters(self):
69  """
70  Get parameters from parfile
71 
72  Raises:
73  ValueError, IndexError, & RuntimeError.
74  """
75  # Load observation container from "inobs" parameter in case that
76  # the observation container is still empty.
77  if self.obs().is_empty():
78  self.obs(gammalib.GObservations(self['inobs'].filename()))
79 
80  # Raise an exception if the observation container is empty
81  if self.obs().is_empty():
82  raise ValueError('No or empty observation provided for '
83  'parameter "inobs".')
84 
85  # Get index of observation to be used
86  index = self['index'].integer()
87 
88  # Raise an exception if the index is not valid
89  if index < 0 or index >= self.obs().size():
90  raise IndexError('Parameter "index=%d" outside the validity '
91  'range [0,%d].' % (index, self.obs().size()))
92 
93  # Get observation
94  self._observation = self.obs()[index]
95 
96  # Make sure we have a CTA observation
97  if not self._observation.classname() == 'GCTAObservation':
98  raise RuntimeError('Input observation not of type '
99  '"GCTAObservation".')
100 
101  # Make sure we have an IRF response associated with the CTA
102  # observation
103  if not self._observation.response().classname() == 'GCTAResponseIrf':
104  raise RuntimeError('Response of input observation not of '
105  'type "GCTAResponseIrf".')
106 
107  # Get calibration database name. If the "caldb" parameter is "NONE"
108  # or empty then use the instrument name from the observation as
109  # calibration database name.
110  self._caldb = self['caldb'].string()
111  if (self._caldb == 'NONE' or len(self._caldb) == 0):
112  self._caldb = self._observation.instrument().lower()
113 
114  # Get instrument response function file name
115  self._outfile = self['outfile'].filename()
116 
117  # Make sure that remaining user parameters are queried now. We
118  # do not store the actual parameter values as we do not want
119  # too many instance attributes with enhances the maintenance
120  # costs.
121  self['irf'].string()
122  self['rootdir'].string()
123 
124  # Write input parameters into logger
125  self._log_parameters(gammalib.TERSE)
126 
127  # Return
128  return
129 
130  def _make_irf_file(self):
131  """
132  Creates an IRF FITS file
133  """
134  # Write header into logger
135  self._log_header2(gammalib.TERSE, 'Creating IRF file')
136 
137  # Get response for the observation
138  rsp = self._observation.response()
139 
140  # Extract response file names
141  fname_aeff = rsp.aeff().filename()
142  fname_psf = rsp.psf().filename()
143  fname_edisp = rsp.edisp().filename()
144  fname_bkg = rsp.background().filename()
145 
146  # Log filenames
147  self._log_header3(gammalib.NORMAL, 'IRF input files')
148  self._log_value(gammalib.NORMAL, 'Effective area', fname_aeff.url())
149  self._log_value(gammalib.NORMAL, 'Point spread function', fname_psf.url())
150  self._log_value(gammalib.NORMAL, 'Energy dispersion', fname_edisp.url())
151  self._log_value(gammalib.NORMAL, 'Background rate', fname_bkg.url())
152 
153  # Open FITS files of response components
154  fits_aeff = gammalib.GFits(fname_aeff)
155  fits_psf = gammalib.GFits(fname_psf)
156  fits_edisp = gammalib.GFits(fname_edisp)
157  fits_bkg = gammalib.GFits(fname_bkg)
158 
159  # Get extension names
160  ext_aeff = fname_aeff.extname("EFFECTIVE AREA")
161  ext_psf = fname_psf.extname("POINT SPREAD FUNCTION")
162  ext_edisp = fname_edisp.extname("ENERGY DISPERSION")
163  ext_bkg = fname_bkg.extname("BACKGROUND")
164 
165  # Create empty FITS file
166  fits = gammalib.GFits()
167 
168  # Append IRF component to FITS file
169  fits.append(fits_aeff[ext_aeff])
170  fits.append(fits_psf[ext_psf])
171  fits.append(fits_edisp[ext_edisp])
172  fits.append(fits_bkg[ext_bkg])
173 
174  # Log resulting FITS file
175  if self._logNormal():
176  self._log(str(fits))
177  self._log("\n")
178  if self._logExplicit():
179  self._log(str(fits[ext_aeff].header()))
180  self._log("\n")
181  self._log(str(fits[ext_psf].header()))
182  self._log("\n")
183  self._log(str(fits[ext_edisp].header()))
184  self._log("\n")
185  self._log(str(fits[ext_bkg].header()))
186  self._log("\n")
187 
188  # Return fits file
189  return fits
190 
191  def _make_caldb_index(self):
192  """
193  Creates an IRF index FITS file
194  """
195  # Write header into logger
196  self._log_header2(gammalib.TERSE, 'Creating database index file')
197 
198  # Open calibration database index
199  indx_file = self._base_dir + '/caldb.indx'
200 
201  # Open index file (or create one if it does not exist)
202  cif = gammalib.GFits(indx_file, True)
203 
204  # Retrieve "CIF" table
205  if cif.contains('CIF'):
206  table = cif.table('CIF')
207 
208  # ... or create binary table if no "CIF" table exists yet, append
209  # binary table to CIF file and return a reference to the appended
210  # table to work with
211  else:
212  table = cif.append(calutils.create_cif_table())
213 
214  # Check if output config already exist
215  has_config = False
216  row_index = -1
217  for caldir in table['CAL_DIR']:
218  row_index += 1
219  if caldir == self._cal_dir:
220  has_config = True
221  break
222 
223  # Create columns if not available
224  if not has_config:
225  # Append 4 rows to CIF extension
226  table.append_rows(4)
227  row_index = table.nrows()
228  else:
229  row_index += 4
230 
231  # Add generic information for these 4 rows
232  for i in range(4):
233 
234  # Set row number
235  row = i + row_index-4
236 
237  # Set date
238  now = str(datetime.datetime.now())
239  today, time = now.split()
240 
241  # Set element
242  table['TELESCOP'][row] = self._mission
243  table['INSTRUME'][row] = self._caldb
244  table['DETNAM'][row] = 'NONE'
245  table['FILTER'][row] = 'NONE'
246  table['CAL_DEV'][row] = 'ONLINE'
247  table['CAL_CLAS'][row] = 'BCF'
248  table['CAL_DTYP'][row] = 'DATA'
249  table['CAL_VSD'][row] = today
250  table['CAL_VST'][row] = time.split('.')[0]
251  table['REF_TIME'][row] = 51544.0
252  table['CAL_QUAL'][row] = 0
253  table['CAL_CBD'][row] = 'NAME('+self['irf'].string()+')'
254  table['CAL_DATE'][row] = today.replace('-','/')[2:]
255  table['CAL_DIR'][row] = self._cal_dir
256  table['CAL_FILE'][row] = self._outfile
257 
258  # Add effective area information
259  row = row_index-4
260  table['CAL_CNAM'][row] = 'EFF_AREA'
261  table['CAL_DESC'][row] = self._caldb+' effective area'
262 
263  # Add point spread function information
264  row = row_index-3
265  table['CAL_CNAM'][row] = 'RPSF'
266  table['CAL_DESC'][row] = self._caldb+' point spread function'
267 
268  # Add energy dispersion information
269  row = row_index-2
270  table['CAL_CNAM'][row] = 'EDISP'
271  table['CAL_DESC'][row] = self._caldb+' energy dispersion'
272 
273  # Add background information
274  row = row_index-1
275  table['CAL_CNAM'][row] = 'BKG'
276  table['CAL_DESC'][row] = self._caldb+' background'
277 
278  # Log resulting FITS table and header
279  if self._logNormal():
280  self._log(str(table))
281  self._log('\n')
282  if self._logExplicit():
283  self._log(str(table.header()))
284  self._log('\n')
285 
286  # Return CIF FITS file
287  return cif
288 
289  def _make_dirs(self):
290  """
291  Make CALDB directories
292  """
293  # Write header into logger
294  self._log_header2(gammalib.TERSE, 'Creating directory structure')
295 
296  # Create calibration database
297  caldb = gammalib.GCaldb(self['rootdir'].string())
298 
299  # Set calibration directory
300  self._cal_dir = 'data'
301  self._cal_dir += '/'+self._mission.lower()
302  self._cal_dir += '/'+self._caldb.lower()
303  self._cal_dir += '/bcf/'+self['irf'].string()
304 
305  # Set absolute path
306  self._base_dir = caldb.rootdir() +'/data'
307  self._base_dir += '/'+self._mission.lower()
308  self._base_dir += '/'+self._caldb.lower()
309 
310  # Set directory for irf file
311  self._rsp_dir = caldb.rootdir() + '/' + self._cal_dir
312 
313  # Log resulting FITS table
314  self._log_value(gammalib.NORMAL, 'Calibration directory', self._cal_dir)
315  self._log_value(gammalib.NORMAL, 'Base directory', self._base_dir)
316  if not os.path.isdir(self._rsp_dir):
317  name = 'IRF directory'
318  else:
319  name = 'IRF directory (existing)'
320  self._log_value(gammalib.NORMAL, name, self._rsp_dir)
321 
322  # Create IRF directory is it does not yet exist
323  if not os.path.isdir(self._rsp_dir):
324  os.makedirs(self._rsp_dir)
325 
326  # Return
327  return
328 
329 
330  # Public methods
331  def process(self):
332  """
333  Process the script
334  """
335  # Get parameters
336  self._get_parameters()
337 
338  # Write input parameters and header into logger
339  self._log_header1(gammalib.TERSE, 'Creating CALDB entry')
340 
341  # Create directory structure
342  self._make_dirs()
343 
344  # Create/update calibration database
345  self._caldb_inx = self._make_caldb_index()
346 
347  # Create response file
348  self._irf_fits = self._make_irf_file()
349 
350  # Return
351  return
352 
353  def save(self):
354  """
355  Save calibration database FITS file
356  """
357  # Write header into logger
358  self._log_header1(gammalib.TERSE, 'Save calibration database')
359 
360  # Set response filename
361  filename = self._rsp_dir + '/' + self._outfile
362 
363  # Write filenames into logger
364  self._log_value(gammalib.NORMAL, 'CALDB index file',
365  self._caldb_inx.filename().url())
366  self._log_value(gammalib.NORMAL, 'Response file', filename)
367 
368  # Save caldb index file
369  self._caldb_inx.save(self._clobber())
370 
371  # Save response file
372  self._irf_fits.saveto(filename, self._clobber())
373 
374  # Stamp response file
375  self._stamp(filename)
376 
377  # Return
378  return
379 
380 
381 # ======================== #
382 # Main routine entry point #
383 # ======================== #
384 if __name__ == '__main__':
385 
386  # Create instance of application
387  app = csobs2caldb(sys.argv)
388 
389  # Execute application
390  app.execute()