ctools 2.1.0
Loading...
Searching...
No Matches
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# ==========================================================================
21import sys
22import os
23import datetime
24import gammalib
25import ctools
26from cscripts import calutils
27
28
29# ================= #
30# csobs2caldb class #
31# ================= #
32class 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
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# ======================== #
384if __name__ == '__main__':
385
386 # Create instance of application
387 app = csobs2caldb(sys.argv)
388
389 # Execute application
390 app.execute()