ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
csadd2caldb.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 # ==========================================================================
3 # Add IRFs to CALDB
4 #
5 # Copyright (C) 2021-2022 Juergen Knoedlseder
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 os
22 import sys
23 import glob
24 import tarfile
25 import gammalib
26 import ctools
27 
28 
29 # ================= #
30 # csadd2caldb class #
31 # ================= #
32 class csadd2caldb(ctools.cscript):
33  """
34  Add IRFs to CALDB
35  """
36 
37  # Constructor
38  def __init__(self, *argv):
39  """
40  Constructor
41  """
42  # Initialise application by calling the base class constructor
43  self._init_cscript(self.__class__.__name__, ctools.__version__, argv)
44 
45  # Return
46  return
47 
48 
49  # Private methods
50  def _get_parameters(self):
51  """
52  Get parameters from parfile
53  """
54  # Query parameters
55  self['indir'].filename()
56  self['outdir'].filename()
57 
58  # Write input parameters into logger
59  self._log_parameters(gammalib.TERSE)
60 
61  # Return
62  return
63 
64  def _collect_tarfiles(self, indir):
65  """
66  Collect tarfiles
67 
68  Parameters
69  ----------
70  indir : string
71  Input directory
72 
73  Returns
74  -------
75  tarfiles : list of str
76  List of tarfiles
77  """
78  # Initialise list of tarfiles
79  tarfiles = []
80 
81  # Search for tarfiles in current directory
82  files = glob.glob('%s/*.FITS.tar*' % indir.url())
83  tarfiles.extend(files)
84 
85  # Search for tarfiles in 'fits' subdirectory
86  files = glob.glob('%s/fits/*.FITS.tar*' % indir.url())
87  tarfiles.extend(files)
88 
89  # Return tarfiles
90  return tarfiles
91 
92  def _extract_irfs(self, file, outdir):
93  """
94  Collect IRFs from tarfile
95 
96  Parameters
97  ----------
98  file : str
99  Tarfile with IRFs
100  outdir : str
101  Output directory
102 
103  Returns
104  -------
105  irfs : list
106  List of IRF dictionaries
107  """
108  # Initialise list of extracted IRFs
109  irfs = []
110 
111  # Open tarfile
112  tar = tarfile.open(file)
113 
114  # Loop over all members in tarfile
115  for member in tar.getmembers():
116 
117  # Skip member names starting with a dot
118  if member.name[0] == '.':
119  continue
120 
121  # Get subarray
122  subarrays = ''
123  nsubarrays = 0
124  if 'LST' in member.name:
125  subarrays += '_LST'
126  nsubarrays += 1
127  if 'MST' in member.name:
128  subarrays += '_MST'
129  nsubarrays += 1
130  if 'SST' in member.name:
131  subarrays += '_SST'
132  nsubarrays += 1
133  if nsubarrays > 1:
134  subarrays = ''
135 
136  # Extract attributes
137  if 'South-' in member.name:
138  site = 'South'
139  else:
140  site = 'North'
141  if '180000s' in member.name:
142  duration = '50h'
143  elif '18000s' in member.name:
144  duration = '5h'
145  elif '1800s' in member.name:
146  duration = '0.5h'
147  if 'SouthAz' in member.name:
148  orientation = '_S'
149  azimuth = 180.0
150  elif 'NorthAz' in member.name:
151  orientation = '_N'
152  azimuth = 0.0
153  else:
154  orientation = ''
155  azimuth = 90.0
156  if 'D25' in member.name:
157  suffix = '_D25'
158  elif 'D27' in member.name:
159  suffix = '_D27'
160  else:
161  suffix = ''
162 
163  elements = member.name.split('-')
164  zenith = [e for e in elements if 'deg' in e][0].strip('deg')
165  instrument = '%s%s_z%s%s_%s%s' % (site, suffix, zenith, orientation, duration, subarrays)
166 
167  # Build output directory
168  path = '%s/%s' % (outdir, instrument)
169 
170  # Write header
171  self._log_header3(gammalib.TERSE, 'Extract IRFs from "%s"' % member.name)
172  self._log_value(gammalib.NORMAL, 'Instrument', instrument)
173 
174  # Extract file
175  tar.extract(member, path='%s/%s' % (self['outdir'].filename().url(), path))
176 
177  # Create IRF record
178  record = {'path': path, 'instrument': instrument, 'file': member.name,
179  'site': site, 'duration': duration, 'zenith': zenith, 'azimuth': azimuth,
180  'orientation': orientation}
181 
182  # Add record
183  irfs.append(record)
184 
185  # Close tarfile
186  tar.close()
187 
188  # Return
189  return irfs
190 
191  def _add_irf_to_cif(self, prod, hdu, irf):
192  """
193  Add IRF to calibration index file HDU
194 
195  Parameters
196  ----------
197  prod : str
198  Production
199  hdu : ~gammalib.GFitsBinTable
200  CIF HDU table
201  irf : dict
202  IRF dictionary
203  """
204  # Set list of IRF component names
205  #names = ['EA', 'PSF', 'EDISP', 'BKG']
206  components = [{'name': 'EA', 'CNAM': 'EFF_AREA', 'DESC': 'CTA effective area'},
207  {'name': 'PSF', 'CNAM': 'RPSF', 'DESC': 'CTA point spread function'},
208  {'name': 'EDISP', 'CNAM': 'EDISP', 'DESC': 'CTA energy dispersion'},
209  {'name': 'BKG', 'CNAM': 'BKG', 'DESC': 'CTA background'}]
210 
211  # Initialise CIF row index
212  row = hdu.nrows()
213 
214  # Append rows for all components to CIF extension
215  hdu.append_rows(len(components))
216 
217  # Add information for all components
218  for component in components:
219 
220  # Set calibration information
221  cal_name = 'NAME(%s)' % irf['instrument']
222  cal_version = 'VERSION(%s)' % prod
223  cal_cut = 'CLASS(BEST)'
224  cal_analysis = 'ANALYSIS(CTA)'
225  cal_zenith = 'ZENITH(%.3f)deg' % float(irf['zenith'])
226  cal_azimuth = 'AZIMUTH(%.3f)deg' % float(irf['azimuth'])
227  cal_bounds = [cal_name, cal_version, cal_cut, cal_analysis, \
228  cal_zenith, cal_azimuth]
229 
230  # Set generic information
231  hdu['TELESCOP'][row] = 'CTA'
232  hdu['INSTRUME'][row] = gammalib.toupper(prod)
233  hdu['DETNAM'][row] = 'NONE'
234  hdu['FILTER'][row] = 'NONE'
235  hdu['CAL_DEV'][row] = 'ONLINE'
236  hdu['CAL_CLAS'][row] = 'BCF'
237  hdu['CAL_DTYP'][row] = 'DATA'
238  hdu['CAL_VSD'][row] = '2014-01-30'
239  hdu['CAL_VST'][row] = '00:00:00'
240  hdu['REF_TIME'][row] = 51544.0
241  hdu['CAL_QUAL'][row] = 0 # 0=good, 1=bad, 2=dubious, ...
242  hdu['CAL_DATE'][row] = '14/01/30'
243 
244  # Set component specific information
245  hdu['CAL_DIR'][row] = irf['path']
246  hdu['CAL_FILE'][row] = irf['file']
247  hdu['CAL_CNAM'][row] = component['CNAM']
248  hdu['CAL_DESC'][row] = component['DESC']
249  hdu['CAL_XNO'][row] = 1
250  for i in range(9):
251  if i >= len(cal_bounds):
252  hdu['CAL_CBD'][row,i] = 'NONE'
253  else:
254  hdu['CAL_CBD'][row,i] = cal_bounds[i]
255 
256  # Increment row index
257  row += 1
258 
259  # Return
260  return
261 
262  def _create_cif_table(self):
263  """
264  Create Calibration Database Index File binary table
265 
266  Returns
267  -------
268  table : ~gammalib.GFitsBinTable
269  Calibration Database Index File binary table
270  """
271  # Create binary table
272  table = gammalib.GFitsBinTable()
273 
274  # Append columns. Reference: CAL/GEN/92-008
275  table.append(gammalib.GFitsTableStringCol('TELESCOP', 0, 10))
276  table.append(gammalib.GFitsTableStringCol('INSTRUME', 0, 10))
277  table.append(gammalib.GFitsTableStringCol('DETNAM', 0, 20))
278  table.append(gammalib.GFitsTableStringCol('FILTER', 0, 10))
279  table.append(gammalib.GFitsTableStringCol('CAL_DEV', 0, 20))
280  table.append(gammalib.GFitsTableStringCol('CAL_DIR', 0, 70))
281  table.append(gammalib.GFitsTableStringCol('CAL_FILE', 0, 128)) # Extend beyond standard
282  table.append(gammalib.GFitsTableStringCol('CAL_CLAS', 0, 3))
283  table.append(gammalib.GFitsTableStringCol('CAL_DTYP', 0, 4))
284  table.append(gammalib.GFitsTableStringCol('CAL_CNAM', 0, 20))
285  table.append(gammalib.GFitsTableStringCol('CAL_CBD', 0, 70, 9))
286  table.append(gammalib.GFitsTableShortCol('CAL_XNO', 0))
287  table.append(gammalib.GFitsTableStringCol('CAL_VSD', 0, 10))
288  table.append(gammalib.GFitsTableStringCol('CAL_VST', 0, 8))
289  table.append(gammalib.GFitsTableDoubleCol('REF_TIME', 0))
290  table.append(gammalib.GFitsTableShortCol('CAL_QUAL', 0))
291  table.append(gammalib.GFitsTableStringCol('CAL_DATE', 0, 8))
292  table.append(gammalib.GFitsTableStringCol('CAL_DESC', 0, 70))
293 
294  # Set keywords. Reference: CAL/GEN/92-008
295  table.extname('CIF')
296  table.card('CIFVERSN', '1992a', 'Version of CIF format')
297 
298  # Return table
299  return table
300 
301  def _add_irfs_to_cif(self, prod, irfs):
302  """
303  Add IRFs to calibration index file
304 
305  Parameters
306  ----------
307  prod : str
308  Production
309  irfs : list
310  List of IRFs
311  """
312  # Build calibration database index name
313  fname = '%s/data/cta/%s/caldb.indx' % (self['outdir'].filename().url(), prod)
314 
315  # Open calibration database index file
316  cif = gammalib.GFits(fname, True)
317 
318  # If file has no CIF extension than create it now
319  if not cif.contains('CIF'):
320  cif.append(self._create_cif_table())
321 
322  # Get calibration database index HDU
323  cif_hdu = cif.table('CIF')
324 
325  # Loop over all IRFs and add information to CIF
326  for irf in irfs:
327  self._add_irf_to_cif(prod, cif_hdu, irf)
328 
329  # Save and close CIF
330  cif.save(True)
331  cif.close()
332 
333  # Return
334  return
335 
336  def _add_irfs(self):
337  """
338  Add IRFs to calibration database
339  """
340  # Collect tarfiles
341  tarfiles = self._collect_tarfiles(self['indir'].filename())
342 
343  # Raise an exception if no tarfiles were found
344  if len(tarfiles) == 0:
345  msg = ('No tarfiles found in folder "'+self['indir'].filename().url()+'. '
346  'Please specify a folder that contains IRF tarfiles.')
347  raise RuntimeError(msg)
348 
349  # Add IRFs in tarfiles
350  for file in tarfiles:
351 
352  # Extract Production
353  fname = os.path.basename(file)
354  elements = fname.split('-')
355  prod = [e for e in elements if 'prod' in e][0]
356  version = [e for e in elements if 'v' in e][0]
357  prod = '%s-%s' % (prod, version)
358 
359  # Build output directory
360  outdir = 'data/cta/%s/bcf' % (prod)
361 
362  # Write header and log parameters
363  self._log_header2(gammalib.TERSE, 'Extract IRFs')
364  self._log_value(gammalib.NORMAL, 'Tarfile', file)
365  self._log_value(gammalib.NORMAL, 'Production', prod)
366  self._log_value(gammalib.NORMAL, 'Target directory', outdir)
367 
368  # Extract IRFs from tarfile
369  irfs = self._extract_irfs(file, outdir)
370 
371  # Add IRFs to calibration index database
372  self._add_irfs_to_cif(prod, irfs)
373 
374  # Return
375  return
376 
377 
378  # Public methods
379  def process(self):
380  """
381  Process the script
382  """
383  # Get parameters
384  self._get_parameters()
385 
386  # Write header
387  self._log_header1(gammalib.TERSE, 'Add IRFs to CALDB')
388 
389  # Add IRFs to CALDB
390  self._add_irfs()
391 
392  # Return
393  return
394 
395 
396 # ======================== #
397 # Main routine entry point #
398 # ======================== #
399 if __name__ == '__main__':
400 
401  # Create instance of application
402  app = csadd2caldb(sys.argv)
403 
404  # Execute application
405  app.execute()